2017년 6월 28일 수요일

당분간 steemit 에서 활동합니다. 다시 여기로 돌아올지는 모르겠네요 @_@

https://steemit.com/@acidsound 입니다.
폰에서 이미지 올리는게 편했는데. 써보다가 불편하면 다시 돌아오죠뭐.

2017년 6월 23일 금요일

raspberryPi zero 에서 USB 네트웍 연결이 안될 때.

집에선 안되고 밖에선 잘되는 알 수 없는 USB 네트웍 연결
raspberryPi Zero가 연결 안된 상태에서 ping 을 했을 때
$ ping raspberrypi.local
PING raspberrypi.local (218.38.137.28): 56 data bytes
64 bytes from 2xx.xx.xxx.xx: icmp_seq=0 ttl=53 time=3.452 ms
64 bytes from 2xx.xx.xxx.xx: icmp_seq=1 ttl=53 time=2.664 ms
64 bytes from 2xx.xx.xxx.xx: icmp_seq=2 ttl=53 time=3.632 ms
64 bytes from 2xx.xx.xxx.xx: icmp_seq=3 ttl=53 time=5.183 ms
64 bytes from 2xx.xx.xxx.xx: icmp_seq=4 ttl=53 time=3.374 ms
64 bytes from 2xx.xx.xxx.xx: icmp_seq=5 ttl=53 time=3.516 ms
^C
이렇게 다른 놈이 잡고 있어서다.
다른 네트웍(가령 테더링)으로 연결 후
ssh pi@raspberrypi.local
로 접근(기본 암호는 raspberry) 후 잘되면
sudo vi /etc/hostname
하여 기존 raspberrypi 를 적절한 것으로 변경하고 raspberryPi Zero를 재부팅 후 ssh pi@<변경한 hostname>.local ( <>는 입력하지 않음) 으로 접속한다.

2017년 6월 15일 목요일

Cycle.js 의 Driver에 대한 이야기

왜 이름이 Driver 인가
OS에서 외부하드웨어와 연결하는 소프트웨어를 Driver라고 하는데 외부로부터 영향(effect)를 주고 영향을 받는다는 점에서 아이디어를 얻음.

DOM Driver


Sink가 없는 형태의 Driver
function WSDriver(/* no sinks */) {
  return xs.create({
    start: listener => {
      this.connection = new WebSocket('ws://localhost:4000');
      connection.onerror = (err) => {
        listener.error(err)
      }
      connection.onmessage = (msg) => {
        listener.next(msg)
      }
    },
    stop: () => {
      this.connection.close();
    },
  });
}
websocket의 예

Driver 만드는 법
function myDriver(sink$, name /* optional */)
부터 시작.

다시 Sock(가짜 실시간 리얼타임 채널 API) 구현
// Establish a connection to the peer
let sock = new Sock('unique-identifier-of-the-peer');
// Subscribe to messages received from the peer
sock.onReceive(function (msg) {
  console.log('Received message: ' + msg);
});
// Send a single message to the peer
sock.send('Hello world');
이렇게 일단 가정.
effect가 어떤 것인지 가려내보자
write effect는 sock.send(msg) 일테고
read effect는 sock.onReceive
import {adapt} from '@cycle/run/lib/adapt';
function sockDriver(outgoing$) {
  outgoing$.addListener({
    next: outgoing => {
      sock.send(outgoing);
    },
    error: () => {},
    complete: () => {},
  });
  const incoming$ = xs.create({
    start: listener => {
      sock.onReceive(function (msg) {
        listener.next(msg);
      });
    },
    stop: () => {},
  });
  return adapt(incoming$);
}
구현은 이렇게.
adapt를 가져와서

  1. outgoing 스트림을 인자로 받는다.
  2. outgoing 스트림에 대해 Listener(subscriber)를 추가한다.
  3. subscriber는 outgoing 스트림에서 받아 sock.send 를 한다.
  4. incoming 스트림은 start에 sock이 데이터를 받을 때 해당 인자(listener)의 next로 받은 메시지를 보낸다.
  5. incoming 스트림을 adapt의 인자로 반환하는 것으로 마무리
여기까지가 sockDriver 라면 Sock을 생성하는 것을 포함한 makeSockDriver를 만들어본다.
import {adapt} from '@cycle/run/lib/adapt';
function makeSockDriver(peerId) {
  let sock = new Sock(peerId);

  function sockDriver(outgoing$) {
    outgoing$.addListener({
      next: outgoing => {
        sock.send(outgoing));
      },
      error: () => {},
      complete: () => {},
    });
    const incoming$ = xs.create({
      start: listener => {
        sock.onReceive(function (msg) {
          listener.next(msg);
        });
      },
      stop: () => {},
    });
    return adapt(incoming$);
  }
  return sockDriver;
}
makeSockDriver는 peerId라는 인자를 받아 Sock을 생성한다.

실제 사용.
function main(sources) {
  const incoming$ = sources.sock;
  // Create outgoing$ (stream of string messages)
  // ...
  return {
    sock: outgoing$
  };
}
run(main, {
  sock: makeSockDriver('B23A79D5-some-unique-id-F2930')
});
익숙한 방식이다.
https://github.com/Widdershin/cycle-animation-driver/blob/master/src/driver.js
requestAnimationFrame 을 사용한 Driver 를 보면서 응용의 폭을 생각해보자.

https://www.npmjs.com/package/cycle-canvas
Canvas에도 마찬가지로 적용할 수 있다.

https://github.com/cyclejs-community/cycle-canvas/blob/master/examples/flappy-bird/index.js
에서 KeysDriver도 흥미롭다.
function makeKeysDriver () {
  const keydown$ = Observable.fromEvent(document, 'keydown');
  function isKey (key) {
    if (typeof key !== 'number') {
      key = keycode(key);
    }
    return (event) => {
      return event.keyCode === key;
    };
  }
  return function keysDriver () {
    return {
      pressed: (key) => keydown$.filter(isKey(key))
    };
  };
}
이렇게 정의하고
function App ({Canvas, Keys, Time}) {
  ...
  const space$  = Keys.pressed('space');
  ...
}
이렇게 사용한다. space 키에 대한 이벤트에 대해서만 filter한 스트림이 space$가 된다.
Driver에 대해 이해하면 Cycle.js 가 더욱 가깝게 느껴진다.

https://github.com/cyclejs-community/cycle-canvas/blob/master/examples/flappy-bird/app.js
flappy bird 예제인데 state에서부터 반복적으로 발생하는 스트림, 화면 갱신 주기. 이 모든 걸 scan하는 것 등등 참으로 알차고 값진 예제다. 이해하기도 쉽고.

Meteor DDP 완전분석 - All about DDP

한번은 정리해야지 하는 생각으로 Websocket 모니터를 열고 DDP를 하나하나 각개 격파해보려고 한다.
클라이언트에서 요청별로 서버 응답 내용을 정리해보았다.


  • 로그인 없을 때
  1. 접속

    14:07:20.485   {'msg':'connect','version':'1','support':['1','pre2','pre1']}
    14:07:20.486 a {'server_id':'0'}
    14:07:20.634 a {'msg':'connected','session':'wqr3vuoJSYXvHLKyR'}

    정해진 JSON을 단순하게 전송하고 { msg: connected } 를 받는다. 단순!
  2. meteor_autoupdate_clientVersions 요청
    14:07:20.486   {'msg':'sub','id':'nhyS7NmraDAk8Y5Nc','name':'meteor_autoupdate_clientVersions','params':[]}
    14:07:20.636 a {'msg':'added','collection':'meteor_autoupdate_clientVersions','id':'HgGFXuNFEw4TeHb7S','fields':{'current':true}}
    14:07:20.639 a {'msg':'added','collection':'meteor_autoupdate_clientVersions','id':'version','fields':{'version':'3953cc195bf2ea8538cc04c228043b0222c64faa'}}
    14:07:20.639 a {'msg':'added','collection':'meteor_autoupdate_clientVersions','id':'version-cordova','fields':{'version':'none','refreshable':false}}
    14:07:20.640 a {'msg':'added','collection':'meteor_autoupdate_clientVersions','id':'version-refreshable','fields':{'version':'4d68205e4c57f75e9a5ca7e7fd446277ef11e2e4','assets':{'allCss':[{'url':'/merged-stylesheets.css?hash=20ae2c8d51b2507244e598844414ecdec2615ce3'}]}}}
    14:07:20.641 a {'msg':'ready','subs':['nhyS7NmraDAk8Y5Nc']}
    최초의 subscribe이다. meteor_autoupdate_clientVersion 이라는 미리 정의한 collection에 버전 정도 등등을 내려준다.
    subscribe시 요청한 ID 를 {'msg': 'ready' } 시 subs 와 맞춰보는 것으로 한 쌍을 이룬다.
  3. 로그인
  4. 로그인 서비스 설정

2017년 5월 28일 일요일

Firebase Functions - backend 로직을 구축해보자.

아예 functions라는 이름의 HTTP기반 node.js application 도 올릴 수 있다.
잘하면 meteor도 올릴 수 있을 거 같긴한데 일단 나중에 볼 일이고
https://firebase.google.com/docs/functions/get-started 따라 해보자
먼저 init 부터
$ firebase init functions

     🔥🔥🔥🔥🔥🔥🔥🔥 🔥🔥🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥     🔥🔥🔥     🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥
     🔥🔥        🔥🔥  🔥🔥     🔥🔥 🔥🔥       🔥🔥     🔥🔥  🔥🔥   🔥🔥  🔥🔥       🔥🔥
     🔥🔥🔥🔥🔥🔥    🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥   🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥
     🔥🔥        🔥🔥  🔥🔥    🔥🔥  🔥🔥       🔥🔥     🔥🔥 🔥🔥     🔥🔥       🔥🔥 🔥🔥
     🔥🔥       🔥🔥🔥🔥 🔥🔥     🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥     🔥🔥  🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥

You're about to initialize a Firebase project in this directory:

  /Users/spectrum/Documents/js/_firebaseApp/sheepals

Before we get started, keep in mind:

  * You are initializing in an existing Firebase project directory


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

i  .firebaserc already has a default project, skipping

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

✔  Wrote functions/package.json
✔  Wrote functions/index.js
? Do you want to install dependencies with npm now? Yes

물어봐주면 Yes. 그러면 firebase-admin과 firebase-functions를 설치한다.

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

설치 끝.
cd funtions 해서 뭐뭐가 있는지 보자.
index.js
node_modules
package.json
다 있다. 끝내주네. package.json 하고 node_modules 폴더 있는 걸 보면 게임 오버네.
var functions = require('firebase-functions');
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
exports.helloWorld = functions.https.onRequest((request, response) => {
  response.send("Hello from Firebase!");
});
주석을 제거하고 실행해보자.
firebase deploy --only functions
로 functions만 적용이 가능하다.
꽤 시간이 걸리는데 과연 로컬환경 구성 같은 건 없나? 불편불편.

$ firebase deploy --only functions

=== Deploying to 'sheepals-2f2d6'...

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
i  runtimeconfig: ensuring necessary APIs are enabled...
✔  runtimeconfig: all necessary APIs are enabled
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (859 B) for uploading
✔  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: creating function helloWorld...
✔  functions[helloWorld]: Successful create operation. 
✔  functions: all functions deployed successfully!

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/sheepals-2f2d6/overview
Function URL (helloWorld): https://us-central1-sheepals-2f2d6.cloudfunctions.net/helloWorld

하 테스트할 URL까지 뽑아주네.


Dashboard에 가면 이런게 생겼고

잘 작동한다.
Firebase.. 무서운 아이!
로그를 보면
실행하는데 얼마나 걸렸는지도 나오고 Functions별 검색도 아주 잘된다.
그래도 구글이 호구는 아닌게 https://github.com/firebase/functions-samples/tree/master/stripe 같은 예를 보면 non-Google 서비스를 사용할 때엔 유료플랜인 Blaze나 Flame으로 변경해야 되나보다.

2017년 5월 26일 금요일

Firebase 정적 호스팅

이번 google I/O도 그렇고 꾸준히 firebase를 밀고 있다.

디비 뿐만 아니라 정적 호스팅도 해준다는 소식에 혹 해서 살짝 내용을 살펴보았다.
https://firebase.google.com/docs/hosting/quickstart

https://console.firebase.google.com/
먼저 firebase 콘솔로 들어가서 프로젝트를 하나 만들자.
Add Project를 누르면

프로젝트 이름과 지역을 설정할 수 있다.
적절히 넣어주고 Create Project하여 프로젝트를 만들고

당연한 이야기지만 0.10.x 이상의 Node.js를 설치한 상태로 firebase-tools를 먼저 설치하자
npm install -g firebase-tools
좀 시간이 걸린다.
다 되면 폴더를 하나 만들고 그 안에서 init으로 초기화를 하자.
mkdir exam1
cd exam1
firebase init

     🔥🔥🔥🔥🔥🔥🔥🔥 🔥🔥🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥     🔥🔥🔥     🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥
     🔥🔥        🔥🔥  🔥🔥     🔥🔥 🔥🔥       🔥🔥     🔥🔥  🔥🔥   🔥🔥  🔥🔥       🔥🔥
     🔥🔥🔥🔥🔥🔥    🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥   🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥
     🔥🔥        🔥🔥  🔥🔥    🔥🔥  🔥🔥       🔥🔥     🔥🔥 🔥🔥     🔥🔥       🔥🔥 🔥🔥
     🔥🔥       🔥🔥🔥🔥 🔥🔥     🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥 🔥🔥🔥🔥🔥🔥🔥🔥  🔥🔥     🔥🔥  🔥🔥🔥🔥🔥🔥  🔥🔥🔥🔥🔥🔥🔥🔥

You're about to initialize a Firebase project in this directory:

  /Users/spectrum/Documents/js/_firebaseApp/sheepals

? Which Firebase CLI features do you want to setup for this folder? Press Space to select featur
es, then Enter to confirm your choices.
⚠  You have have not selected any features. Continuing will simply associate this folder with a Firebase project. Press Ctrl + C if you want to start over.

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Select a default Firebase project for this directory: sheepals (sheepals-2f2d6)

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

여기선 깨지는데 터미널에서 공격적인 Firebase ASCII ART를 볼 수 있다.
초기화가 끝나고 나면 덜렁 firebase.json과 .firebaserc 두 파일을 생성한다.
firebase init hosting 을 한번 더 해주면
=== Hosting Setup
Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.
? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
✔  Wrote public/index.html
i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
✔  Firebase initialization complete!
요렇게 몇 가지 더 확인하고 public 아래에 index.html 까지 다 만들어 준다.

firebase.json이 구조 정보가 들어가는 파일인데 package.json 같은 거라고 생각하면 된다.
기본적으로
{
  "hosting": {
    "public": "public",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
요런걸 생성하는데 node_modules도 써야하고 .idea, .gitignore 등등 필요없는 파일/디렉토리를 디플로이하지 않기 위해 ignore를 추가하고 public 위치도 ./app으로 바꿔본다.
{
  "hosting": {
    "public": "app",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}
https://firebase.google.com/docs/hosting/deploying 에서 북봍해서 적용해보자.
디폴트 경로는 public이 키인 ./app 아래에 index.html 하나 넣어보자.
ignore에 있는 내용은 실제 디플로이 되지 않는다.
디플로이 하자.
firebase deploy
너무 간단하다. 할말이 없다.
<project명-난수숫자키>.firebaseapp.com 으로 접속할 수 있다.
$ firebase deploy
=== Deploying to 'sheepals-2f2d6'...
i  deploying hosting
i  hosting: preparing app directory for upload...
✔  hosting: 1 files uploaded successfully
i  starting release process (may take several minutes)...
✔  Deploy complete!
Project Console: https://console.firebase.google.com/project/sheepals-2f2d6/overview
Hosting URL: https://sheepals-2f2d6.firebaseapp.com
마지막에 나온 Hosting URL로 접근해서 잘 나오면 오케이.
다음 내용을 보면서 functions와 custom domain을 숙지하고 계속 나아가자.

2017년 5월 10일 수요일

동적으로 로딩하는 Meteor i18n 다국어 적용기

작년부터 꾸려가던 서비스가 다행인지 불행인지 수입은 별론데 입소문과 방송이 마구 돌아서 해외에서도 요청이 들어오고 있다.

드디어 i18n! 즉, 다국어 지원을 해야할 시기가 온 것이다.
https://themeteorchef.com/tutorials/i18n-and-meteor 를 보고 tap-i18n(https://github.com/TAPevents/tap-i18n) 패키지를 써보기로 마음먹었다.

약간 삽질이 있었는데 처음에
meteor add tap:i18n
만 하면 나머지 다 잘 될줄 알았다.

아니었다. 역시 인생 실전...

내 경우는 json 파일을 정적으로 만드는 것이 아니라 개발팀이 아닌 일반 관리자가 새로 만든 상품에 대해서도 다국어 대응을 바로 해야하기 때문에 동적으로 로딩할 수 있도록 만들어야 했다.

먼저, 자원을 로드하는 건 별로 어렵지 않았는데
TAPi18n.loadTranslations(
    {
        es: {
            meteor_status_waiting: "Desconectado"
        },
        fr: {
            meteor_status_failed: "La connexion au serveur a échoué"
        }
    },
    "francocatena:status"
);
이런 식으로 언어별로 key/value 매핑을 하고 마지막 인자로 namespace를 주면 된다.
별로 떠오르는 이름이 없어서 위 예의 "francocatena:status" 대신 "project"라고 줬다.

html 헬퍼에서 {{{_ '번역할 문장'}}} 을 했더니 '_' 라는 펑션 없단다.
ecmascript 패키지와 충돌하느니 어쩌느니 얘기가 주절주절 많은데
결론은 project의 root에 project-tap.i18n 파일을 만들고
{
    "helper_name": "_",
    "supported_languages": ["ko", "en"]
}
이렇게 두 개의 키를 지정하면 된다.
supported_languages를 지정하지 않으면 TAPi18n.setLanguage 를 통해 현재 언어를 변경하는 것이 제대로 작동하지 않으니 주의할 것.