Flutter で Facebook login を実装時にエラー

Flutter で flutter_login_facebook を使い、Facebook login の実装時にエラーが発生した。M1 Macならではの問題かと疑いながら調べていたが、関係はなく、解決できた。

このような感じのエラーが発生した。

`Error output from Xcode build:

objc[88641]: Class AMSupportURLConnectionDelegate is implemented in both ?? (0x2035678f0) and ?? (0x1167702b8). One of the two will be used. Which one is undefined.
objc[88641]: Class AMSupportURLSession is implemented in both ?? (0x203567940) and ?? (0x116770308). One of the two will be used. Which one is undefined.
** BUILD FAILED **
$ flutter clean
$ flutter pub get
$ cd ios
$ pod install
$ flutter build ios

Aws::S3::Client.copy_object の copy_source は、URL encode されていなければならない

例えば、バケットからバケットへオブジェクトをコピーするときに、
`The specified key does not exist.` とエラーが出る場合がありました。

client = Aws::S3::Client.new

client.copy_object(
      bucket: out_bucket_name,
      copy_source: src_bucket_name + '/' + object_key,
      key: object_key,
      acl: 'public-read',
      cache_control: "max-age=#{OBJECT_MAX_AGE}",
      content_type: src_object.content_type,
      metadata_directive: 'REPLACE',
    )

https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#copy_object-instance_method
ドキュメントを見ると、The value must be URL encoded.とあったので、
これは、 copy_source に渡している、object_key に、hogehoge%2B.pngなど、エスケープされていない文字が含まれていると、パスが見つからず、エラーになってしまうようです。

URI.encode_www_form_component(src_bucket_name + '/' + object_key) などで、エンコードしましょう。

vue-scrollto などの smooth scroll を利用する対象に、 nuxt-link を指定すると正しくスクロールしない

nuxt 利用時、URLに hash ( #hoge ) を設定すると同時に、スムーズにするロールするを行いたい場合、一般の方が書いた記事には、to をカラにすればいいと書いてありますが、正しく動作しない条件が存在します。

<div id="note"></div>
<nuxt-link to v-scroll-to="'#note'">noteクリック</nuxt-link>
<div id="memo"></div>
<nuxt-link to v-scroll-to="'#memo'">memoクリック</nuxt-link>

このように記載した上で、URLにハッシュを設定しブラウザをリロード。この状態で、nuxt-link をクリックすると、window.pageYOffset が 0 が入り、スクロール開始位置が間違っているため、とくにスクロール方向が逆になった時に違和感が大きくなってしまいます。フッターに # がついたリンクがあると特に発生しやすいはずです。

vue-scrollto や smooth-scroll が悪いわけではなく、
初回表示時にnuxt-linkをクリックすると、nuxt-link がまず pageYOffset = 0 にスクロール設定することが原因でした。
その後スクロールが開始するため、正し位置からスクロールしません。

対処するためには、以下のように、nuxt-link 代わりの component を作るのがよいのではないでしょうか。

<template lang="pug">
a(:href="to" @click.stop="onClick")
  slot
</template>

<script lang="typescript">
import {
  defineComponent,
  getCurrentInstance
} from '@nuxtjs/composition-api'
import TWEEN from '@tweenjs/tween.js'

type Props = {
  path: string,
  hash: string | null,
  anker: string,
}

export default defineComponent({
  props: {
    path: {
      required: true,
      type: String
    },
    hash: {
      type: String
    },
    anker: {
      required: true,
      type: String
    }
  },
  setup ({ path, hash, anker }: Props, { emit }) {
    const current = getCurrentInstance()

    const scrollTo = (anker: string) => {
      const dom = document.querySelector(anker)
      if (!dom) { return }
      const end = dom.getBoundingClientRect().top + window.pageYOffset

      // tween.js の例
      const data = { y: window.pageYOffset }
      new TWEEN.Tween(data)
        .to({ y: end }, 500)
        .easing(TWEEN.Easing.Exponential.InOut)
        .onUpdate(() => { window.scrollTo({ top: data.y }) })
        .start()

      const animate = (time: number) => {
        if (TWEEN.update(time)) {
          requestAnimationFrame(animate)
        }
      }
      animate(0)
    }
    const to = hash === undefined ? `${path}${anker}` : `${path}${hash}`
    const onClick = () => {
      if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
        // do nothing
      } else
      if (current?.$route.path === path) {
        e.preventDefault()
        window.history.replaceState(null, '', to)
        scrollTo(anker)
      } else {
        e.preventDefault()
        current?.$router.push(to)
      }
    }
    return {
      to,
      onClick
    }
  }
})
</script>

serverless framework で 日本語のPOSTされたデータを文字化けせず受け取る

serverless framework を使い、AWS Lambda や API Gateway を用いたシステムで、multipart/form-data 形式のデータを受け取る場合、日本語データのとき、文字化けが発生する。

元々AWS Lambda は、input / output に text を扱うようになっているので、バイナリデータとして送信される multipart/form-data に、何も設定しないままでは対応できない。

serverless framework を使うときは、まず serverless-apigw-binary を追加。

$ npm i serverless-apigw-binary

serverless.ts や serverless.yml に以下の項目を追加する事で、lambdaに渡すときにbase64エンコードされ、lambda上でアクセスするときには自動でデコードされる。

  custom: {
    apigwBinary: {
      types: [
        'multipart/form-data',
      ]
    },
  }
  // ...
  plugins: [
    'serverless-apigw-binary',
    // ...
  ],

Vue3 を利用するときに、どうやって this.$scrollmagic を取得するのか

Vue2 から Vue3 になり、出来ることは変わりませんが、
以前では root context にアクセスするためには、this という書き方でよかったものが、composition-api に変わった今後、どのように書けばよいのかを書きます。

例えば、this.$scrollmagic と書けていた、pluginsで設定された $scrollmagic などに、どうやってアクセスすればよいのか。

context の中の root から、取得することが出来ますが、こちらは deprecated になっているため、おすすめしません。

export default defineComponent({
  setup (_props, context) {

    // any にするのはおすすめしませんが、簡単に表現するため
    const scrolmagic = (context.root as any).$scrollmagic
  }
})

getCurrentInstance を使い取得する

import {
  defineComponent,
  getCurrentInstance
} from 'vue' 
// or from '@vue/composition-api'
// or from '@nuxtjs/composition-api'

export default defineComponent({
  setup () {
    const root = getCurrentInstance()
    const scrolmagic = (root as any).$scrollmagic
  }
})

AWS copilot でサーバー構築

AWS copilot は、webサーバー、バックグラウンドで常時起動するサーバー、定期ジョブ(Cronのような)が、かなり簡単なymlとコマンドだけでセットアップでき、オートスケールにも対応できます。GCPKubernetesに近いです。
コンテナ内でコマンドを実行し、DBへコマンド実行なども、task コマンドが用意されているため行えます。

$ copilot init
$ copilot env init
$ copilot svc init
$ copilot svc deploy

この程度のコマンドと、Dockerfile があれば、アクセスできるALBのURLが作成されます。

やっていることは、CloudFormation が実行され、リソースが自動で作成される、ビルド&デプロイが実行されるコマンドが用意されている というだけです。
追加で、RDSやCloudFront、Redisなど使いたいリソースを Terraform や手動で作成することになります。

他サービスと比較すると、

  • GCP の Cloud Run では、常時起動が想定されていない
  • Serverless Framework (AWS)では、常時起動ができない、システムに制限がある
  • AWSKubernetes では、ログの出力など所々全自動ではなく面倒
  • GCPKubernetes では、セットアップはほぼ自動だが、ビルド・デプロイはコマンド1つになっていない
  • GCP では、CloudFront ほどのカスタマイズ性、動作のわかりやすいCDNがないため、AWSを利用したい
  • AWS Elastic Beanstalk は、Amazon Linux2などOSが変わったときに、設定ファイル書き換えが必要など、OSやElastic Beanstalkの設定変更に依存してしまう

Serverless Framework などで、大抵のものは対応できますが、さらに一歩進んだサーバー構成が必要なときに、どれも手軽さがないと思っていましたが、ちょうどいいツールとなっていました。
Fargate やALBが起動し、特有の制限もなく使えるため、どんなサーバー構成が必要な場合も使えると思います。

しかし、Terraform などで AWSリソースの定義がすんでいて、ビルド、デプロイまでの環境が作れている場合は、特に利用する必要はないツールです。

最初に困ったこと

  • インストール方法の記載に、古いバージョンが指定されているものがいくつかあります。copilot 1.0.0 以前のバージョンは`copilot init`もろくに動かないため、インストールは1.0.0 以降を利用します。
  • `~/.aws/credentials` に profile を記載しただけでは認識されず、`~/.aws/config` に書かなければcopilotコマンドに認識されません。

今後対応されそうだが困っていること

デプロイ後のヘルスチェックに失敗すると、その後デプロイができなくなる。

https://github.com/aws/copilot-cli/issues/1180
デプロイコマンドがなかなか終わらなかったら、手動でタスク定義を修正し、前のリビジョンを指定する、または必要なタスク数を0に(Webサーバーの場合は当然アクセスできなくなる)すればば再度デプロイできる。

docker build コマンドのARGが、環境ごとに切り替えられない

https://github.com/aws/copilot-cli/pull/1059

M1 Mac で deploy ができない

arm64 でimageをビルドすると、サービスが起動できないようです。

必要最小限なIAM Policy の定義をドキュメントに書いてほしい

調査が面倒で、ALLアクセスにする人が多くなってしまうので、教えてほしい。

デプロイ時に保持されるリビジョンが2つのため、ロールバックできなくなってしまう

そもそもロールバックのコマンドはまだないが、手動でリビジョンを戻せばロールバックできる。しかし、リビジョンが2つしか保持されないため、2回デプロイに失敗すると戻せなくなってしまう。

なんとかなるが困っていること

  • コンテナが書き出すログの、datetime_format を指定できないため、cloudwatch logsに、誤った時間で取り込まれてしまう
  • コンテナ起動時の command がyamlで指定できない

気になった点

CloudFormation を使いたくない

CloudFormationは、内部エラーが起きると、復旧には copilot だけではどうにもならなくなるため、使いたくない気持ちになりました。ただ、AWSのツールなので、AWSのサービスを使わなければいけないはずで、そこが問題の1つかもしれません。

pipineという、CodeBuildとCodePipeline を利用した CI/CD が存在するが、CircleCI や Github Actions を使いたい

こちらもAWSのツールのため自社サービスを使わなければいけないという問題だと思います。
Github Actions で独自にビルド、デプロイを用意するのはとても簡単なので、利用されないかもしれません。

追加リソースを定義するアドオン

S3 をアプリからストレージとして使うことが多いため、S3などを定義し、さらにRoleを設定するアドオンがコマンドで追加できます。
今後も定義できるリソースは増えそうで、便利ではありますが、設定ファイルにCloudFormationの定義ファイルが大量に取り込まれ、見苦しいです。Terraformを使って定義するので、特にいらないと思いました。

GCPKubernetesぐらいの手軽さ+デプロイも楽というツールだと思いました。
Kubernetesをまだ理解していない人には、より簡単に感じる可能性もあります。

同じコンセプトで、AWSに依存しすぎないないツールが生まれた場合には、使われなくなってしまう気がしますが、現状は存在しないので、このツールを積極的に使っていきたいと思っています。

CircleCI や Github Actions などで、 firebase deploy を行う

firebase deploy には、 token を利用した方法と、サービスアカウントを利用した方法がある。

token を利用する記事は多くあるが、問題点があり、特定ユーザーの権限が全て circleci に渡ってしまうため、別プロジェクトも操作できてしまう事と、ユーザーをプロジェクトから外したときに、デプロイができなくなってしまう。

なので、こちらにあるような、`firebase login:ci` でtokenを取得する方法はおすすめしない。

Firebase CLI リファレンス

サービスアカウントを利用したデプロイ

サービスアカウントの作成

これはとても簡単に作成できる。
歯車 > ユーザーと権限 > サービスアカウント > [新しい秘密鍵の生成]
これでjsonファイルが取得できる。

こちらはFirebaseの操作が何でもできるアカウントなので、ここからさらに権限を減らしたサービスアカウントを作る場合もある。

json ファイルを使ったデプロイ

まずはパソコン上でデプロイを試すと思うが、
`GOOGLE_APPLICATION_CREDENTIALS ` この環境に、json ファイルのパスを設定する。
project id は、歯車 > プロジェクトを設定 から確認できる。
あとは、自動で環境変数が読まれ、デプロイすることができる。

export GOOGLE_APPLICATION_CREDENTIALS=******/***.json
firebase deploy --project <project id>

注意点として、すでに firebase login をしている場合は、そちらが優先されてしまうため、かならず firebase logout でログアウトを行う。

デプロイの確認方法

サービスアカウントでデプロイできているかを確認するには、
Hosting > リリース履歴 を確認し、`firebase-adminsdk-****` と書いてあれば、正しくサービスアカウントでデプロイできている。
ユーザーのメールアドレスの場合は、ユーザーアカウントでデプロイしたことになっている。