Firebase HTTPS callable function を試してみる

2018年3月20日の Firebase の更新で、クライアントから直接 Cloud Functions が叩ける機能が入りました。
[Call Functions from Your App  \|  Firebase](https://firebase.google.com/docs/functions/callable)

認証状態を持ったまま Cloud Functions を叩くことができるので、今までのように [authenticated\-json\-api](https://github.com/firebase/functions-samples/tree/master/authenticated-json-api) や [authorized\-https\-endpoint](https://github.com/firebase/functions-samples/tree/master/authorized-https-endpoint) のような自前の認証は不要になりました。

# 使ってみる

iOS + Cloud Functions で使ってみました。

## Cloud Functions

TypeScript の例です。

```ts
export const httpcallable = functions.https.onCall((data, context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
            'while authenticated.')
    }

    if (!data.name) {
        console.log('data.name is not found')
        throw new functions.https.HttpsError('invalid-argument', 'data.name is undefined.', data)
    }

    return {
        uid: context.auth!.uid,
        name: data.name,
        data: data,
        context: context
    }
})
```

Cloud Functions は data, context を受け取ることができます。
data はクライアント側から渡すことのできるパラメータです。

context には認証情報などが含まれています、 context の型をみてみましょう。

```ts
export interface CallableContext {
    auth?: {
        uid: string;
        token: firebase.auth.DecodedIdToken;
    };
    instanceIdToken?: string;
}
```

認証済みだったら auth に値が入ってきます。auth があるか、anonymous かどうかもその内容で判定できます。
実際のパラメータはこのようになっていました。

#### 未ログイン

```
context { instanceIdToken: 'hogehoge-token' }
```

#### anonymous ログイン

```
context { auth: 
   { uid: 'uid',
     token: 
      { iss: 'https://securetoken.google.com/myproj',
        provider_id: 'anonymous',
        aud: 'myproj',
        auth_time: 1521769673,
        user_id: 'uid',
        sub: 'uid',
        iat: 1521782508,
        exp: 1521786108,
        firebase: [Object],
        uid: 'uid' } },
  instanceIdToken: 'hogehoge-token' }
```

firebase のなかみ

```
firebase { identities: {}, sign_in_provider: 'anonymous' }
```

#### facebook ログイン

```
context { auth: 
   { uid: 'uid',
     token: 
      { iss: 'https://securetoken.google.com/myproj',
        name: 'Mike',
        picture: 'https://scontent.xx.fbcdn.net/v/jpg',
        aud: 'myproj',
        auth_time: 1521788688,
        user_id: 'uid',
        sub: 'uid',
        iat: 1521788688,
        exp: 1521792288,
        email: 'hoge@tfbnw.net',
        email_verified: false,
        firebase: [Object],
        uid: 'uid' } },
  instanceIdToken: 'hogehoge-token' }
```

firebase の中はこんな感じ

```
firebase { identities: 
   { 'facebook.com': [ '100258...' ],
     email: [ 'hoge@tfbnw.net' ] },
  sign_in_provider: 'facebook.com' }
```

### 注意

エラーを返す時は throw しましょう。 `return new functions.https.HttpsError('invalid-argument', 'data.name is undefined.', data)` だとクライアント側に無が返されます。 (result も error も nil が返る)

### エラー

下記のエラー型が定義されています。HTTP Status Code のように使いやすく良いですね。

```ts
export declare type FunctionsErrorCode = 
  'ok' | 'cancelled' | 'unknown' | 'invalid-argument' | 
  'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 
  'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' |
  'data-loss' | 'unauthenticated';
```


## iOS

これだけで認証済みで Functions を叩くことができます。便利ですね。

```swift
import FirebaseFunctions

functions.httpsCallable("httpcallable").call(["name": "taro"]) { (result, error) in
  if let error = error as NSError? {
    if error.domain == FunctionsErrorDomain {
      let code = FIRFunctionsErrorCode(rawValue: error.code)
      let message = error.localizedDescription
      let details = error.userInfo[FunctionsErrorDetailsKey]
    }
  }
  if let name = (result?.data as? [String: Any])?["name"] as? String {
    print(name)
  }
}
```

response は result or error になっています。

result.data に Functions が return したデータが入ってきます。
result.context に auth 情報も入っていました。

## 内部実装はどうなっているか

[firebase\-functions](https://www.npmjs.com/package/firebase-functions) の内部実装を読んでみると、HTTP で叩いているだけのようです。
ざっくり読んだだけですが、 [authorized\-https\-endpoint](https://github.com/firebase/functions-samples/tree/master/authorized-https-endpoint) をラップしたメソッドを提供してくれているだけのようです、onCall のコードは 50 行程度なので読んでみると良いと思います。