コネヒト開発者ブログ

コネヒト開発者ブログ

Sign in with AppleでのiOSアプリとサーバーとの連携

こんにちは!エンジニアの柳村です。

Twitterなどの3rd partyのログイン機能を提供しているアプリは6/30までに対応が必要です。(2ヶ月延期されましたね!)

アプリ単体でSign in with Appleをできるようにするのはとても簡単です。しかし大抵のアプリの場合はそれだけでは完結せず、サーバー側でSign in したユーザーと紐付ける必要があります。

サーバー側はFirebase AuthenticationやAuth0といったIDaaSにまかせるという手もありますが、今回は自前で実装することを前提にその実現方法を見ていきたいと思います。

全体の流れ

クライアント側とサーバー側のざっとした流れはこのようになります。

f:id:yanamura:20200327094652p:plain
sign in with apple flow

クライアントからサーバー側にid_tokenを渡すやり方とauthorization_codeを渡すやり方の2つの方法がありますが、ここではauthorization_codeを使ったやり方のみ紹介します。また、nonceの生成やクライアントサーバ間の受け渡し方法についても割愛します。

iOSアプリ側

Apple Developer Programでやること

アプリのIdentifierのCapabilitiesにSign in with Appleを設定します。 その際に、configureボタンを押してPrimary App IDを設定します。

また、Identifierを更新するとProvisioning ProfileがInvalidになってしまうので更新する必要があります。

XcodeのProject設定でやること

TARGETSのSigning&Capabilitiesで+Capabilityを押してSign in with Appleを追加します。

実装

iOSアプリ側でやることは、Sign in with Appleをするボタンを用意し、ボタンが押されたらASAuthorizationAppleIDProviderを生成し、performRequest()を呼ぶとログインに成功するとcallbackが呼ばれ、authorization_codeが取得できるので、これをサーバーに渡すだけです。(ドキュメント: Implementing User Authentication with Sign in with Apple)

ボタンの用意

import AuthenticationServices
...

let appleButton = ASAuthorizationAppleIDButton() // デザインを変えたい場合はUIButtonなどに変えれば良い
appleButton.rx.controlEvent(.touchUpInside)
    .subscribe(onNext: { [unowned self] in
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.email]
        request.nonce = `something` // nonceについては割愛します

        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
    })
    .disposed(by: disposeBag)

delegateの実装

extension XXViewController: ASAuthorizationControllerDelegate {
    @available(iOS 13.0, *)
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        switch authorization.credential {
        case let appleIDCredential as ASAuthorizationAppleIDCredential:
            // ここでauthorizationCodeやidTokenが取得できる。
            print("#apple \(String(data: appleIDCredential.identityToken!, encoding: .utf8))")
            print("#apple \(String(data: appleIDCredential.authorizationCode!, encoding: .utf8))")

            // サーバーにauthorizationCode送る処理
        default:
            break
        }
    }
}

extension XXViewController: ASAuthorizationControllerPresentationContextProviding {
    @available(iOS 13.0, *)
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return view.window!
    }
}

サーバー側

Apple Developer Programでやること

Keysでkeyを追加します。 Sign in with AppleをENABLEにし、configureボタンを押してPrimary App IDを設定します。 作成が完了すると秘密鍵(.p8)をダウンロードします。この秘密鍵と作成したKeyのKey IDは、次でclient secretを作成するのに使います。

サーバー側の実装概要

サーバー側でやることは大きく2ステップで、まずAppleの/auth/token APIを叩いてid token(JWT)を取得します。 次にid tokenをAppleの/auth/keys APIを叩いて取得した公開鍵でverifyしてuser identifierを取得します。

id tokenの取得

1. client secret(JWT)の作成

以下のheaderとpayloadを使ってJWTを生成します。

header

{
  alg: ES256,
  kid: // Apple Developer ProgramのKeysで作成したKeyのKeyID
}

payload

{
  iss: // TEAM ID,
  iat: 今の時間,
  exp: 今の時間+適当な値(max 15777000),
  aud: https://appleid.apple.com,
  sub: // Bundle ID
}

署名はApple Developer ProgramのKeysで作成したときにダウンロードした秘密鍵を使います。

2. /auth/tokenにPOSTする

以下のデータをhttps://appleid.apple.com/auth/tokenにPOSTします。API仕様

{
    client_id: // Bundle ID
    client_secret: // 1. でつくったclient secretをいれる,
    code: , // iOSアプリから受け取ったauthorization_codeをセットする
    grant_type: 'authorization_code',
    redirect_uri: ""// 空文字
}

成功するとid_tokenが含まれたJSONが取得できます。

id tokenのverify

上で取得したid token(JWT)をverifyする必要があります。

1. 公開鍵の取得

https://appleid.apple.com/auth/keysをGETするとJWKSが取得できます。(API仕様

これには複数のJWKが含まれています。この中から、JWKのkidとid tokenのheaderのkidと一致するものを使って公開鍵を生成します。

2. id tokenのverify

ドキュメントVerify the Identity Token に以下のように記載があります。

- Verify the JWS E256 signature using the server’s public key

- Verify the nonce for the authentication

- Verify that the iss field contains https://appleid.apple.com

- Verify that the aud field is the developer’s client_id

- Verify that the time is earlier than the exp value of the token


これに従って、1. で取得した公開鍵を使ってid tokenをverifyし、id tokenのpayloadのnonce, iss, aud, timeを検証します。

id tokenのpayloadに含まれる内容は以下になります。(ドキュメント)

{
  iss: 'https://appleid.apple.com'
  sub: // The unique identifier for the user.
  aud: // Bundle ID
  exp: // expire time
  iat: // The time the token was issued.
  nonce: // nonce
  nonce_supported: true or false, 
  email: // The user's email address.
  email_verified: true or false,
}

subがuser identifierになるので、これを使ってユーザーを識別します。

まとめ

このように、iOSアプリ側は割と簡単に実装できますが、サーバー側は結構やることがあります。Googleみたいにサーバ側用のライブラリが用意されていればよいのですが、残念ながらAppleは用意してくれていません。。Githubにいくつかライブラリがありましたが、実装が間違っている(特にid tokenのverify周り)ものがときどき見受けられたので選定には注意が必要かなと思いました。Sign in with Appleを導入し、かつ自前でサーバー側も実装する場合は余裕を持って取り組んだほうがよさそうかなと思いましたので、まだ3ヶ月くらいありますが早めに対応したほうがよさそうでした。

最後に宣伝です!

コネヒトではエンジニアを募集しておりますので少しでも興味のある人はお話だけでも聞きにきてください! www.wantedly.com

Zoomウェビナーを使ったオンライン勉強会の裏側

こんにちは。2017年11月にAndroidエンジニアとしてjoinした関根です。03/11(水)にリモートワークを考えよう、というテーマでオンラインの勉強会を開催しました。パネリストとオーディエンスのみなさま、誠にありがとうございました!今回はその勉強会の配信の裏側を書かせて頂きます。

なお、当日の様子とLTスライドは下記の記事内に掲載しています。 www.wantedly.com

開催する上で考えたこと

今回のイベントは普段のオフラインの勉強会とは違い下記のような制約がありました。

  • パネリストがオンラインかつ複数拠点で発表できること
  • オーディエンスがなるべく手間をかけずに参加できること
  • オンラインでもLTに対して質疑応答が可能なこと
  • オンラインでも参加者全員がコラボレーションの体感が得られること

これらの制約をクリアし、どのようなツールでどのようなコミュニケーション設計にすれば、普段の勉強会と変わらない体験を提供できるかのかを考えるところから始めました。

どのツールを利用するか

結論から書くと今回はタイトルにある通りZoomウェビナーを利用しました。 選択した理由としては下記の点です。

  • 画面共有機能でスライドを共有できるのでパネリストが集まる必要がなかったこと
  • 質疑応答機能、チャット機能、手を上げる機能など双方向のコミュニケーションを取る機能が充実していたこと
  • 視聴者はアプリのインストールが不要で参加できること

コネヒトでZoomを試験導入中で使い慣れていたことや、ビデオ通話が高品質で安定していたことが理由としてありますが、先述した制約の全てを解消することができたのでZoomを選んで正解だったと考えています。注意点ですが、Zoomウェビナーはプランにより最大参加人数が決まっているので、最適なプランを選びましょう。*1

配信トラブルがあった場合

オンラインイベントがはじめてだったので、イベント時に配信トラブルが起きることも考えられました。Zoomウェビナーにはレコーディング機能があり、後日動画を公開することが可能で安心感がありました。 録画したものはYoutubeに公開していますので、是非ご覧ください! www.youtube.com

コミュニケーション設計はどうするか

オフライン勉強会を開催が決定して一番悩んだのが、コミュニケーション設計をどうするかでした。 普段の勉強会とは違い全参加者がオンラインでコミュニケーションをとる必要があるので、普段の勉強会とは違った難しさがありました。

参加者の役割と可能な操作

まず簡単にZoomウェビナーの各参加者の役割を解説します。 この役割のメンバーがそれぞれにどのようにコミュニケーションをとってもらいリモートワークへの理解を深めるかを考えなければいけませんでした。

役割 ビデオ通話 チャット Q&A投稿 手を挙げる機能
ホスト オペレーター できる できる 回答のみ できない
パネリスト 登壇者 できる できる 回答のみ できない
オーディエンス 観客 パネリストに昇進することで可能 できる 質問と質問への投票 できる

これを前提のどのようなコミュニケーションを取ることが最適か相談し方針を決めていきました。概ね以下のような議論を行いました。

コミュニケーションの課題と対策

オフラインでのリアクションをオンラインでどのように再現するか?

オフラインの勉強会では自然と生まれる拍手や歓声がオンラインでは難しいので以下のような状況が想定されました。

  • パネリストにオーディエンスの反応が伝わりにくい
  • 全参加者が勉強会に参加してる感が薄まる

これはチャット機能を利用し、👏👏👏👏👏👏👏や888888888のようなコメントを書き込んでもらうことで登壇者に参加者のリアクションが伝わるような工夫をしました

質疑応答はどのように実施するか?

オフラインの勉強会で行われているのと同じように、質疑応答の時間を取らないとリモートワークの知識を深める場としての意義が少し低下してしまう様に思いました。これについてはZoomウェビナーの機能を踏まえ以下の2つの案があがりました。

  1. 質疑応答機能を利用して、テキストで質問する。
  2. 手を挙げる機能を押したオーディエンスのビデオ通話を許可し直接質問してもらう

これはZoomが初めての参加者が多くいることが予測されたので、オーディエンスがよりシンプルな操作で質問できる2.の案を採用しました。

ホストとパネリストのコミュニケーションはどうするか?

オフラインのイベントでは、イベント中のコミュニケーションは、カンペを出す、耳打ちをするなどで取ることが可能ですが、今回はパネリストの方がそれぞれ別の場所にいるため、他の方法を取る必要がありました。これは以下のような案があがりました。

  1. Slackなどチャットツールで連絡を取り合う
  2. Zoom内でホストとパネリストのみへの発言機能を利用する

結論としてZoomウェビナーのチャット機能では誤操作の可能性があったため、Slackでコミュニケーションを取る方法を採用しました。

これ以外にも相談したことは数多くあるのですが、紹介しきれないのでここまでとし、次は当日の流れを紹介します。

当日の流れ

当日は以下のタイムラインで進行をして行きました。

17:30 - 18:30(リハーサル)
18:50 - 19:00(前説)
19:00 - 19:40(LT&質疑応答)
19:40 - 19:45(結び)

それぞれの様子を簡単に紹介して行きます。

リハーサル

Zoomウェビナー機能の実践セッションを利用しホストとパネリストで、リハーサルを行いました。 パネリストの皆様とイベント開始前に簡単な打ち合わせをしながら下記の確認をしました。

  • 画面共有の操作手順の確認
  • 映像と音声の見え方聞こえ方チェック
  • 質疑応答の流れの共有

他にも質疑応答時の流れをリハーサルしたり、オーディエンスからの見え方を確認するなど、ギリギリまでバタバタとしながら本番に臨みました。

前説

司会からオーディエンスに向けて以下のような説明を行いました。

  • 登壇者の画面がみれているかの確認
  • リアクションの取り方の説明
    • チャットの画面を開いて、リアクションの練習をしてもらう
  • 質疑応答の説明
    • 手をあげる機能を利用してもらう
    • ホスト側でビデオ通話を許可するので直接質問してもらう

開始時に会場側でハウリングが発生したり*2、司会の映像が遅れるなどのトラブルもありましたが、参加者のみなさんに、前説からチャットで盛り上げていただいたので、あたたかい気持ちでスムーズにイベントを開始することができました。

LT&質疑応答

概ねスムーズに進行できたと思いますが、はじめてのオンラインイベントらしく、下記のようなちょっとしたトラブルもありました。

  1. 手を挙げる機能を利用した質疑応答で、立候補者がでなかった
  2. オーディエンスのチャット設定がパネリストだけに見えるようになっていた

これらのトラブルにも以下のように、参加者の皆さまのご協力により、それぞれ対応をすることができました。

  1. パネリストの方にチャットから質問を選んでもらうやり方に切り替える
  2. 司会からチャットの設定を変更するようにオーディエンスに促してもらう

パネリストとオーディエンスの皆さま、スムーズなイベント進行にご協力いただきありがとうございました!!

反省点

今回はじめてオンライン勉強会を開催したため、様々な点で反省はあるのですが、特に質疑応答の体験をもう少しよくできたと感じています。次回開催時はZoomの質疑応答機能を利用してみたいと思っています。 最後となりますが、今回のイベントの経験オンライン勉強会用のチェック項目を掲載し、本記事の締めとさせて頂きます。*3

オンライン勉強会用チェック項目
事前準備

- 周りの音が入り込まない静かな環境を用意できるか
  - ヘッドフォンがあると安心
- 配信で利用するPCは安定して映像音声を届けられるか
  - 可能ならば予備のPCを用意しておく
- オンラインでのリアクションを決める
- 質疑応答の方法/流れを決める
 - オーディエンスへの説明の方法も一緒に決める
- 録画機能を利用するかを決める
  - 事後配信をするかも一緒に
- 参加者に配信URLを共有するタイミングを決める
- リハーサル時間を当日に用意する
   - 特に質疑応答の手順は複雑なので入念に

本編中

 - ハウリングに気をつけよう
 - オーディエンスのコメント設定は全員向けになっているか
   - デフォルトではパネリストにだけ見える設定
 - 周りの雑音に気をつけよう
 - 録画機能状況に気をつけよう
 - 配信画面への映り込みに気をつけよう

ここまでお読み頂きありがとうございました!

*1:ZoomミーディングのアドオンなのでZoomミーディングのプロプラン以上の契約も必要です。

*2:犯人は私です

*3:Zoomウェビナーの文脈が強めです

リモートワークについて考える勉強会(オンライン開催)でもらった質問にすべて答えます

こんにちは、エンジニアの@dachi_023です。03/11(水)にリモートワークを考えよう、というテーマでオンラインの勉強会を開催しました。発表・参加していただいた皆さん、ありがとうございました!発表資料や雰囲気などは下記の弊社広報ブログにまとまっていますのでこちらもぜひご覧ください。

www.wantedly.com

近い内に録画した内容に字幕等つけたものをアーカイブ配信する予定ですので途中からしか見れなかった方や気になった方はそちらをお待ちください(というのを気軽にできるのもオンライン配信のいいところだな、と思いました)。

チャットでもらった質問について

今回はZoomのウェビナー機能を使って発表&その場でQAをしたんですが、時間の関係ですべての質問にはお答えすることができなかったので本投稿ですべてお答えしていきます。

※ 発表時に回答した質問についても再掲(もしくはより詳細に回答)しています。

職種によってリモート率とか変わったりしますか?

「この部は全然リモートしてないね」とかはないです。webサービスの会社なので基本はリモートワークでも働けます。ただ、会社に用事がある場合等は各々の判断で通勤のピークタイムを避けつつ出社しています。

sneekで背景見られたくない方とかはどうしてるんでしょうか?

(Zoomにはバーチャル背景があるが、他のサービスにはない。どうするか?という話)

Snap Cameraのフィルターには、顔の加工だけではなく背景も加工してくれるものがあるのでそういうのを使うといいかもしれません。

f:id:dachi023:20200312212651p:plain
例えばこれはナゲットなんですが、背景付きなので部屋が映りません

リモートワークで不便はありますか

導入直後はツールの使い方に慣れていないため、通話中に一斉に喋ってしまったりとかしてワチャワチャしてしまうことが多かったです。あとは子供が仕事中に絡んできたりするので最初は大変でした。

リモートワーク推進メンバーは、どうやって選定されましたか?

立候補した人で構成されています。そのメンバーで使えそうなツール候補のリストを作り、検証しながらどれを正式採用しましょうかね?という話を進めていきました。

リモートワークでも勤務時間ってあるんですか?

コアタイムがあるのでその時間内は確実に働いているようにしています。それ以外は家庭の状況などに合わせて働いています。なので、勤務時間の管理はオフィスワークしていた頃と変わらずやっています(打刻はSlack上からやっています)。

リモートで使用するツールの導入判断で、意外と重要なポイントってなにかありましたか?

リモートワークに限らないのですが「安定しているか」は大事だと思いました。例えば、Slackが頻繁に落ちるサービスだったらコミュニケーションが全然取れなくなってしまうリスクがあるので選びません、といった感じです。あとはツールを増やしすぎないように気をつけました。もちろん必要なら入れるんですが、複数のツールを組み合わせないといけないとなると単純に面倒なので・・・。

リモートワークを導入するのに大変だったことはありますか?

どういう心持ちでリモートワークすればいいだろうね、とかどういうツール使うと仕事が捗りそうかな、とかそういうドキュメントを沢山作ったのでその辺は結構時間がかかったかもしれません。あとは、私が対応したわけではないのですが、セキュリティまわりの対応とかは大変そうだなあと思って見ていました。

各自、在宅での誘惑はどのように向き合うようにしていますか?

私の場合はテレビが絶対見ることのできない方向にデスクを置いてます。今は目の前に壁があって、壁を見るかディスプレイを見るか、くらいしか選択肢がありません。あとは環境音とかがなるべく聞こえないように&通話したい時にすぐ繋げられるようにワイヤレスイヤホンを付けっぱなしにしています。

Zoom慣れない方へのレクチャーで工夫したことはありますか?

ミーティングの始め方はどうやるか?というレクチャーは厚めにやりました。Zoomはミーティングの設置だけでも設定する項目が多く面倒なので、ZoomのSlack App使ってミーティング開始するとログイン以外は不要で簡単ですよ!とかGoogle Calendarの予定にZoomのURLを紐付けると探しやすいよ!とかそういう話をしました。簡単に始められそうだな!という感覚を持ってもらうのを大事にしました。

初めてリモートワークをする人や、社内の人の反応はどうですか?

そこまで不便はしてなさそうかな?と思っています。不便だなと思った点などは共有してもらってなるべく早めに改善するようにして、ずっと不便な状態にはならないように気をつけています。

Zoom のホスト権限の管理などはうまく回ってらっしゃるのでしょうか

現時点だと推進メンバー、マネージャー以上のメンバー、人事や営業など外部の方とミーティングを設定する必要がある人に付与しています。マネージャー以上のメンバーとしているのはマネージャーに付与することで各チームに1人以上はホストがいる状況が作れるからです。

リモートのタスク管理とかで、問題とかありますか?

Asana, Trello, GitHub Project, ZenHubなど、チームごとに使っているものは異なりますがオンライン上で管理しているので特に困ることはありませんでした。あとはデイリースクラムや朝会などをZoomで開催し、タスクの確認をしてチーム内の認識を揃えるようにしています。

リモートで仕事外のコミュニケーションはどう取っていますか??(リモートランチや飲み会とか)

飲み会はやったことないですがランチはやりました(ちょうどこの発表当日の昼休みでZoomで繋いでランチしました)。最初は億劫になりがちなんですが慣れてくると良いものです。

管理が行き届かない場合のトラブル例とかはありますか?

コネヒトではトラブルとかは発生していないんですが、例えばメンバーの管理という観点で見ると「サボらないんですか?」はよく聞かれます。個人の意見としては、そこを管理する必要はない(= サボった分あとで頑張らないといけないのはサボった人なので・・・)と思います。モノの管理の話だと、リモートワークのために自宅に会社の備品などを持ち帰る必要があったりするのでその辺の管理は各自お願いしますね、とかでしょうか。

さいごに

以上となります!予想以上に多くの質問をいただくことができました。また、皆さんのリモートワークへの関心の高さも伺えたような気がしています。今後も引き続き参考になりそうな情報を発信していきます💪

PR

先月書いたブログでもまとめていますのでこちらも良ければ読んでみてください。

tech.connehito.com

社内向けにまとめていたリモートワークの知見をすべて公開します

こんにちは、エンジニアの@dachi_023です。先週くらいから新型コロナウイルスの感染拡大に伴って在宅リモートが導入された、という内容のプレスやニュースをよく見かけるようになりました。コネヒトでも同様に在宅リモートの推奨、ラッシュ時の電車通勤は原則禁止、などのアナウンスがあり、これまでリモートワークをしていなかったメンバーもリモートワークを導入して自宅から働いています。

ですが、いきなり「皆さんそれではリモートワークしてください!はいどうぞ!」と言われても家からどのように働けば良いのか・・・となり、ただただ生産性が落ちてしまうだけです。もちろん健康第一で導入されている今回のリモートワークではありますが、出来ることなら効率をなるべく落とすことなく働きたいです。

コネヒト開発部では今年の頭からリモートワークの試験導入を行っており、エンジニアとデザイナーが週に2日ほどリモートワークしてどういったツールを使うと良いかとか、ビデオ会議やチャットでのコミュニケーションを改善して業務が円滑に進むようにするためにはどうしたら良いかなどの調査や検証をしていました。本記事ではその検証結果を公開し、今回の件で初めてリモートワークを導入した企業の方々の参考になれればと思っています。

リモートワーク導入にあたり理解しておきたいこと

在宅で働くことはオフィスに来て働くことの上位互換ではないです。通勤がなくなるのでそこに関しては最高なんですが、チャットはやり取りに少し時間がかかったり、ビデオ通話は多少とはいえ遅延があり最初のうちは会話しづらく、オフラインで対面で話すのに比べてコストがかかることもあります。などなど、それぞれで得意なこと、不得意なことがあります。そうした状況をリモートワークするメンバー、オフィスにいるメンバーがそれぞれ理解して、協力しながら進めていく必要があります。

リモートワークはノリだけで始めると辛い

色んな記事で散々書かれていることですが、リモートワークは「スキル」です。どうやったら物理的に離れているメンバー同士でうまいこと協力していけるか、を考えて実践し、改善していかなければいけません。個人で改善できることもありますし、会社としてリモートワークが快適に行えるような土台を作ることも必要になってきます。会社にいる時と同じやり方では上手くいかないことも多々発生するのでそういった点を解決していくのが導入後すぐに発生する仕事になると思います。

チェックリストを作成する

全社向けに自宅の環境やリモート時の意識についてのチェックリストを作成しました。これによって何があった方がいいのか、在宅で仕事をするためにやっておくべきことをある程度把握してもらいたい、という狙いがあります。

自宅環境の整備について

必須なもの

  • ✅ インターネット回線は契約してあるか
    • モバイルルーターなどでも可 (ただし光回線の方が速い・安定する)
    • 自身の業務を行うのにストレスのない回線速度が出ているか
  • ✅ 作業スペースは確保できているか
    • 近くにPC等を充電できるようコンセントがある
  • ✅ ビデオ会議用のイヤホンはあるか
    • 自分が使いやすいものであれば何でもOK
    • 移動する際とか面倒なのでワイヤレスがオススメ
  • ✅ カメラはあるか
    • カメラがついているノートPCであればそれを利用する
    • ついていないモデルの場合は購入することをおすすめします
  • ✅ 誘惑を断ち切れるか
    • テレビなどのコンテンツに意識を持っていかれていないか
  • ✅ 残業時間を気にする意識はあるか
    • いつでも働ける状態なので気をつける
    • 適切な業務量と時間を維持する
  • ✅ 家族、パートナーに在宅で働くことが理解されているか
    • 業務時間中に家事をお願いされる
    • 話しかけられる頻度が多い
    • 働かないと・・・でも家の事も・・・みたいになって辛い

あればなお良い

  • ✅ Wi-Fiが使えるか
  • ✅ 長時間座っていても身体が痛くなりにくいデスク、椅子などはあるか
  • ✅ 外付けディスプレイはあるか

オフィスにいるメンバー向け

リモートワークしているメンバーと一緒に働くためには以下に注意しましょう。

  • ✅ オフラインでしか得られない情報を無くす
    • 会社来い!って言ってるのと変わらなくなっちゃうので
    • 必ずオンラインのどこかで確認できるようにすること (Google Drive, 情報共有サービス, など)
  • ✅ 勤怠などについて監視しすぎない
    • 所詮カメラで映している部分しか動きは見れないし、互いに疲弊するのでやめる
    • プロセスよりも最終的なアウトプットを評価する
    • ただし、今働いているかどうかは知れたほうがいいので始業・休憩・終業くらいはルール化する

使用しているツール

リモートワーク時に使用しているツールは下記の通りです。

2つ書いてあるところはまだ検証している途中のもので、部署やチームによって使っているツールがバラバラな状態です。最終的にはどちらかに統一される予定になっています。

以下、必須ではないものの使ってみたら意外と良かったものを紹介します。

ゆるい繋がりを生み出すツール

SneekとPukkaTeamは一定間隔で写真を撮ってチームメンバーの現在の状況を共有するためのツールです。どちらもモザイク機能があり顔や部屋を映したくない場合にも利用できます。効果は人による、といった感じで特にメリットを感じない人もいますが、見られている感があって仕事をやる気になる、という人もいました。どちらのツールも機能はほとんど一緒なのですが、Sneekはウィンドウサイズに合わせて写真のサイズが変わるので小さくして画面端に置いておくのに便利でした。

ノイズキャンセリング

Krispをインストール後にZoomやHangoutsの入力側として設定すると環境音などを軽減してくれます。今のところ軽減が確認できたのは以下です。

  • テレビの音
  • 外を走っている車の音
  • 強風時の風の音

テレビに関しては結構な音量で流れていたにも関わらず実際に通話した人に聞いてみると「たまにうっすらと聞こえる程度で差し障りはない」とのことだったのでそれなりに効果がありそうでした。

f:id:dachi023:20200221210936p:plain
Zoomの例。krisp microphoneを選択することで有効になる

スピーカー側の設定もあるのですが、普段使っているイヤホンでノイズキャンセリングを有効にしたら音声がブツブツと切れるようになってしまったため私は無効にしています。

チャット上のコミュニケーション

普段から利用しているSlackでのコミュニケーションにおいても、今一度意識してみてねということで社内に展開したものが以下になります。なのでリモートワークだからやってくださいね、という内容ではないです。

利用頻度を落としすぎない

  • ひとりでいるとチャット画面と向き合う時間が少なくなりがち
    • 周りに人がおらず、ずっと集中しているので大事な用がある時くらいしか開かなくなりがち
    • リモート解禁でただでさえコミュニケーションコストが上がるので更に上がらないようにする
    • いつでも話しかけられる、話しかけられやすい状態を作れるよう意識する
  • また、通話やビデオ会議の際によそよそしくならないようにしておく
    • この人と久々に話したかも、となると気まずい感じになったりするのでチャットコミュニケーションも大事にする

とりあえず反応する

  • メンションなど来た時にとりあえず反応だけしておくと相手が見てくれていることを確認できる
    • 「後でちゃんと返します!」でもいいのでやると良い
    • 都度ちゃんと返す、をやり始めるとずっとチャットに張り付くことになる
      • 非同期コミュニケーションの良さがなくなるためそれは避けたい
  • 共有だけの連絡などに関してもEmojiでリアクションするなりして見たよ、が分かるようにする

雑談も大事にする

  • 対面で話す機会が減るのでそこを補うようなイメージ
  • コネヒトのSlack上には趣味などに関するチャンネルがあるのでそういったものも活用していく

不安を抱えておかない

  • 誰も気づかないまま自分だけがモヤモヤしていってしまうため
  • 書けばだいたい誰かが拾ってくれるので積極的に書く

察してくれは無理

  • 物理的に離れているため表情や雰囲気はほとんど分からない
    • ビデオ通話してたとしても見えている範囲は限られるのでオフラインに比べるとやはり弱い
  • 書いたことしか伝わらない、くらいの感覚でいたほうが良い
  • テキストのやり取りは含められる情報(感情とか)が少ないのでなるべく伝わりやすい文章にする

やりとりの数を減らす

  • 1回で伝えられる量を意識しないと何度も質問をすることになり時間がかかる
  • Yes / Noで答えられるような投げかけを意識するなど、工夫してコミュニケーションを取るようにする
  • また、話が長引きそうだなと思った時に速やかに通話に切り替えるなどができればそれでも良い

チャットだけに頼らない

  • チャットでのコミュニケーションは気軽だが前述したとおり情報が他の手段に比べ少なくなりやすい
  • より深く話し合いたい内容がある時などは通話等の手段を使えるようにしておく
    • = 通話に対しての苦手意識を減らしておく
    • 通話もオフィスで対面で話すのも同じだとイメージすると良い

ビデオ会議中のコミュニケーション

リモートワーク解禁によって初めて利用する人も多いであろうビデオ会議に関しても意識しておきたいことをまとめて展開しました。

1人1台PCを用意して通話を繋ぐ

  • オフィスにいる人達が1台だけ接続して通話するのは良くない
  • 3人以上だともう無理。2人なら全員映るからまだ許せる
  • 1台に複数人張り付いている場合
    • 死角で喋っている人が誰なのかがリモート側からだと分からない
    • マイクからの距離が遠くなりがちなので声が聞こえなかったりする
  • 誰が発言しているのか、が分かりやすい状態にしておく

オフィスメンバー、1箇所に集まる必要なし

  • 全員イヤホンして自席やフリスペでそれぞれ話せば良い
  • 集まるとどうしても物理的に近い側が優位になりリモート側が隔離されるタイミングがある
  • あと、会議室をいちいち取らなくて良くなる
    • 自席などで周りに人がいる状況で独り言的に話すのは最初恥ずかしいと思うけどそのうち慣れます

喋らない時はミュートにする

  • イヤホンせずにスピーカーから音声を流している場合は特にハウリング、山彦するので常に意識してミュートを忘れないようにする
  • Zoomでは音が出ている人が分かる機能があり、それの活用にもなる

同時に喋らない

  • オフラインでの会話と違い映像・音声共に遅延が発生するので非常にコミュニケーションしづらくなる
  • 誰かが喋っている時、他メンバーはミュートにして聞くことに徹すると会話がしやすくなる
  • 交代制にしやすくするために喋り終わったら「以上です」などを付けるようにすると分かりやすい

会場は通話部屋

  • 会議室やオフィスは会場ではない
  • HangoutsやZoomに作成した通話部屋が会議の会場である、という認識を持つ
  • 画面越しの人たちも会議に参加していることを意識してコミュニケーションをとる

なるべく物理ツールを使わない

  • 共有がしづらい、デジタル化しづらいため極力避ける
  • ホワイトボードが使いたいならMiroなどを使うと良い
    • オンライン上で共同編集が可能、閲覧もそのまま出来る

共有できるものはする

  • 議事録や資料など、URLを先に伝えておいたり、画面共有したりする
  • 「聞き取れなかった」「さっきの資料を再確認したい」といったメンバーがいることを考慮する

大人数での議論は避ける

  • ROM専(聞いているだけの人)が増えるだけなので大人数集まるのは非常に効率が悪い
  • 肌感だが頑張っても8人くらいが限界だと思う
    • 少なければ少ない方が楽ではある
    • 8人に収まらないような会議はそもそも会議の内容もメンバーも分割した方がいい

喋りますの合図を決めておく

  • あると便利くらいの感じで別に必須ではない
  • 手を挙げるとか、Zoomであればリアクション機能を使うなどして合図を決めておくと誰が喋るかが分かりやすい
  • ファシリテーターがいればそれで良い場合もある

さいごに

たまたま開発部がリモートワークの検証期間中であったため、そこまで大きな混乱もなく導入、良いスタートダッシュが切れています。ですが、改善できる箇所はまだ沢山残っているので、今後も改善を続けていき今よりも高いパフォーマンスを発揮していけるようにしたいです。もし皆さんの会社でも導入しようか考えている、導入したが上手く機能していないと感じた時、この記事を読んでいただいて何か参考になるものがあれば良いなと思っています。

*1:GSuite版のHangoutsです

*2:Slack上から打刻する機能を利用しています

Swift製CLIツールをMintを使わずSwiftPMで管理する

こんにちは!エンジニアの柳村です。

SwiftLintやSwiftFormat,XcodeGenといったSwift製のコマンドラインツールを管理するために、同じくSwift製であるMintを使っているのを割とよく見かけます。 Mintは便利ですが、Mint自体をなにかしらの手段でインストールしなければならないという問題がでてきます。

そこでMintは使わずSwift Package ManagerでSwift製のコマンドラインツールを管理する方法をご紹介します。

方法

Package.swiftを用意します。 nameを適当に設定し、dependenciesに利用したいコマンドラインツールのリポジトリのurlと必要であればバージョンを指定します。

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "Tools",
    dependencies: [
        .package(url: "https://github.com/apple/swift-format", .branch("master")),
        .package(url: "https://github.com/yonaskolb/XcodeGen", from: "2.13.0"),
        .package(url: "https://github.com/mac-cain13/R.swift", .exact("5.1.0")),
    ]
)

あとはswift runするだけです。

$ swift run -c release swift-format --help

最初の実行時はコマンドラインツールのビルドが行われるため結構時間がかかりますが、2回目以降は.build/release以下にビルド結果が残っているのでビルドなしで実行できます。

Swift Package Managerでツール以外のパッケージも管理している場合

Swift Package Managerを使っていないプロジェクトの場合は、なにも考えずにプロジェクトのルート直下にPackage.swiftを配置しても特に問題ないと思いますが、既にSwift Package Managerを使ってる場合は対処が必要です。

なぜなら、Swift Package Managerはdebugビルドやtestのときのみのdependenciesを設定する機能がないため、dependenciesに書いたものは必ずダウンロードとビルドが実行されてしまうからです。そのためリリースビルドなどが無駄に遅くなってしまったりします。

対処方法

その1. ディレクトリを分ける

コマンドラインツール系は別のディレクトリにおいてしまうのが手っ取り早い方法です。

例えば、cliというディレクトリをプロジェクト内につくって、そのディレクトリ内にコマンドラインツールのdependenciesを書いたPackage.swiftを配置しswift runを実行すればよいです。swift run --package-path cli xcodegenというふうに--package-pathでPackage.swiftのあるディレクトリを指定すればcliディレクトリに移動せずにswift runすることができます。

その2. 別の名前のPackage.swiftを用意して実行時にPackage.swiftに置き換える

Swift Package Manager製のコマンドラインツールの開発で使われたりするやり方で、例えばPackage.test.swiftというテスト用のファイルを用意しておいて、テストするときだけPackage.test.swiftをPackage.swiftに置き換えます。

普通のiOSアプリ開発の場合は、Package.release.swiftというファイルを用意しておいて、CIなどでリリースビルドするときだけPackage.release.swiftをPackage.swiftに置き換えてやるとよいのではないかと思います。

ただ、2つのファイルをメンテしなければならないといったデメリットがあります。

Mintとの違い

最後にMintとの違いについて触れておきます。

Mintはglobalに保存

Swift Package Managerの場合はPackage.swiftのあるディレクトリの.build以下にcloneやビルド結果が保存されますが、 Mintの場合は/usr/local/lib/mint/以下に保存されます。複数のプロジェクトがあり、他のプロジェクトと同じバージョンのパッケージを使っていた場合はキャッシュが効くメリットがあります。

Mintはglobal linkつくるのが楽

MintはCLIを実行するrunだけでなく、globalにinstallするinstallという機能があります。 どいういうことかというと、例えばmint install yonaskolb/XcodeGenを実行しておくと、/usr/local/bin以下に実行ファイルのリンクがつくられるので、mint run xcodegenとしなくても、xcodegenだけで実行できるようになります。

Mintはconfig fileなしで実行できる

Swift Package Managerの場合Package.swiftを用意しなければなりませんが、Mintの場合はMintfileなしでも使えます。

まとめ

基本的にはMint使わなくてもSwift Package Managerで事足りるのではないかなと思っています。

ただ、SwiftLintの0.38.xはビルド通らないようでした(https://github.com/realm/SwiftLint/issues/2867)のでご注意ください。(0.39.0で治りました)

そしてコネヒトではエンジニアを募集しておりますので少しでも興味のある人はお話だけでも聞きにきてください! www.wantedly.com

コネヒトはPHPerKaigi 2020にプラチナスポンサーとして協賛いたします!

こんにちは。エンジニアの 高野 (@fortkle) です。
さて、今日はイベント協賛の告知をさせてください!

PHPerKaigi 2020に協賛いたします

タイトルにもある通り、PHPerKaigi 2020 にプラチナスポンサーとして協賛いたします。
コネヒトではメインプロダクトである「ママリ」を始めとして開発のメイン言語としてPHPを活用しており、フレームワークとしてはCakePHPを採用しています。

イベント概要

PHPerKaigiはどんなイベントか、公式サイトから拝借すると

PHPerKaigi(ペチパーカイギ)は、PHPer、つまり、現在PHPを使用している方、過去にPHPを使用していた方、これからPHPを使いたいと思っている方、そしてPHPが大好きな方たちが、技術的なノウハウとPHP愛を共有するためのイベントです。

という 過去・現在・未来PHPerに向けたお祭り のようです! 他のカンファレンスと同様に公募トークがありつつ、数人で特定のテーマについてディスカッションするInteractive Round Tableや、アンカンファレンスなどちょっと違ったことも企画されているとのこと!

タイムテーブルもすでに発表されているので興味がある方は要チェックです!

タイムテーブル | PHPerKaigi 2020 #phperkaigi - fortee.jp

コネヒトからは2名登壇します

@itosho 2020/02/09 16:55〜 Track A 15分トーク
登壇タイトル:「Deep Module in PHP」

fortee.jp

@fortkle 2020/02/11 15:00〜 Track A 15分トーク
登壇タイトル:「GitHub Actionsで始めるPHPアプリケーションのCI実践入門」

fortee.jp

ご参加される方はぜひ発表を聞いていただければ幸いです!

チケット購入はこちらから

phperkaigi.jp

最後に

ここまで読んでくださった皆様、ありがとうございました。
そして、 PHPerチャレンジ中の皆様、お目当てのPHPerトークンはこちらです!

#EnjoyPHPWithConnehito

それでは!

コネヒトではPHPerを積極採用中です!

Connehito Image PHPとAWSで開発!生活領域における課題を解決したいエンジニア募集!

2019年の忘年会を機械学習APIを使って盛り上げた話

こんにちは!
忘年会エンジニアのたかぱい(@takapy)です。

コネヒトの忘年会は例年気合いが入っており、有志メンバーが運営となり1年の締めくくりを最高のものにすべく、尽力しています。

今回は2019年の忘年会を技術で盛り上げるべく、同じく忘年会エンジニアのあぼ氏(@abo)と奮闘したことをご紹介できればと思います。


目次


パシャりんピック(既存構成)の紹介

前回(2018年)忘年会時に、弊社エンジニアにより「パシャりんピック」という歓談タイムを盛り上げるコンテンツが爆誕しました。

ざっくり説明すると、

  • 社員同士でツーショット写真を取る
  • 社員1人1人に点数が初期設定されており、写真に写る人物によってその写真のスコアが算出される
  • 最終的に獲得スコアの一番高い人の勝ち(たくさん撮った人が有利)

というものです。(詳しくは下記参照)

tech.connehito.com

このコンテンツの評判がとても良かったので、今回はこれをアップデートさせる形で忘年会を盛り上げてやろうと画策しました。

今回のコンセプト

そもそものコンセプトが明確だったので、今回はこれに「笑顔」要素と順位と写真の見える化をプラスすることで、より盛り上がるコンテンツに仕上げようと決めました。

アップデート内容

大別して下記2点です。

  • リーダーボードの実装(主にあぼ担当)
  • スコア算出方法のアップデート(主にたかぱい担当)

それぞれについて、全体の構成図も交えながら説明していきます。

アーキテクチャ

以下のようなアーキテクチャを構築しました。

f:id:taxa_program:20200117180243p:plain
システムアーキテクチャ

使用した技術は下記です。

  • GCP
  • Azure
  • Python
  • Firebase
  • Flutter

リーダーボードについて(byあぼ)

今回は"人"と"写真"の2つに対して点数がつくので、点数上位の人と写真がリアルタイムで見られるようにリーダーボード(スコアボード)を作りました。

f:id:aboy_perry:20200120113657j:plain
リーダーボード

使用技術ですが、今回はFlutterをweb上で動かしました。Flutter on the webは現状beta版ではありますが、

  • 私がFlutterを趣味で使っているので知見があり、好きなので開発するやる気が出る
  • 保守性を考えなくても良いアプリケーションである
  • 最悪ローカルの環境で特定のブラウザでさえ動けば良い

という理由で採用しました。

基本的にリーダーボード側はFirestoreから購読した値を画面に表示するだけなので、そこまでやることは多くありません。Firestoreの購読にはStreamBuilderを使って、UIはRowとColumnを駆使して組み立てていきました。

また、リーダーボードのメインのUIとは別に、当日の忘年会進行スライドとの統一感を出すために、画面右上に忘年会のロゴを表示させました。今回はStackという、Widgetを重ねられるWidgetを使って実現しました。Stackを使うことで"ロゴ"表示とメインコンテンツの"スコア"表示部分のレイアウトが分割されるので、個人的にはこっちのほうが好みです。

class BoardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Positioned(
            top: -20,
            right: -50,
            child: Image.asset(
              'logo.png',
            ),
          ),
          // メインコンテンツのスコア表示UIを組み立てる
          Column(
            ~~~
          ),
        ],
      ),
    );
  }
}

Firebaseの機能を使えるようにする

それから、Firebaseの機能を使うのに一工夫必要だったので紹介します。まず、Flutter on the webはFirebase Hostingで動かしました。設定はfirebaseコマンドを使ってサクサクと行えます。firebase.jsonではpublicにbuild/webを指定します。これはflutter build webでweb用のリリースビルドが生成される場所です。

{
  "hosting": {
    "site": "year-end-party-2019-board",
    "public": "build/web",
    "ignore": [
      "firebase.json"
    ]
  }
}

次に、Dart packagesからFirebaseパッケージをインストールするのに加えてweb/index.htmlのscriptタグでjsファイルを読み込ませるようにしました。この辺はFirebaseをJavaScriptアプリケーションにインストールするドキュメントを参考にしました。これでFirebaseの機能が使えるようになりました。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  firebase: ^7.1.0

web/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>year-end-party-2019-board</title>
</head>
<body>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

スコア計算について (byたかぱい)

忘年会エンジニア兼・機械学習エンジニアなので、スコア算出に機械学習の要素を取り入れました。

「笑顔」という時点で察している方もいらっしゃると思いますが、写真に写る人物の笑顔度合いを数値化してスコア算出を行いました。

本来であれば、複数の写真(学習データ)を用意して、笑顔度のアノテーションをして、学習させて・・・というフローが必要なところですが、今のご時世APIを1つ叩くだけでやりたいことができます。

という訳で、笑顔の数値化にはAzure Face APIを使用しました。

Azure Face APIには、Python SDKがあり、普段Pythonを触っている人であれば比較的スムーズに使用できるのではないかと思います。

Azure Face APIの使用例

例えば、画像をpostしてレスポンスを受け取る場合は下記のように記述します。

FACE_API_URL = 'https://japaneast.api.cognitive.microsoft.com/face/v1.0/detect'
KEY = 'hogehoge'
HEADER = {'Ocp-Apim-Subscription-Key': KEY}

def call_api(image_url):
    params = {
        'returnFaceId': 'false',
        'returnFaceLandmarks': 'true',
        'returnFaceAttributes': 'smile,emotion'
    }

    response = requests.post(
                            FACE_API_URL,
                            params=params,
                            headers=HEADER,
                            json={"url": image_url}
                            )

    return response


# image_urlには画像のURLを設定
response = call_api(image_url)

ここから笑顔の値を取得したい場合は下記のように記述します。

smile = response.json()[0]['faceAttributes']['smile']

これで笑顔度(0〜1)を取得することができます。

Azure Face APIが笑顔に寛大すぎる問題

試しに何枚か写真を撮って笑顔度を算出してみると、ある問題に気付きました。。。

「割と簡単に最高値が取得できてしまう🤔」

これでは盛り上がるコンテンツにならないのでは?と思い、とあるロジックを追加しました。

それは「写っている人物の鼻端が近ければ近いほど、高得点になる」というもので、これを「仲の良さ」と銘打ってスコア計算に組み込みました。
(近い位置で写真撮ってる方が仲良さそうですよね?そうでしょ?)

f:id:taxa_program:20200115104828p:plain
当日のルール説明に使用した資料

鼻端のx座標、y座標に関しては上記のAPIを使用すれば取得できるので、「近さ」の定義だけ考えました。

「近さ」の定義

通常であればユークリッド距離を計算すれば良いのですが、ハックできる要素を含ませた方がコンテンツとして面白いのでは?と思い、x座標の差分の絶対値を「近さ」の定義として計算することにしました。

どうすればハックできるかはもちろん伝えていなかったのですが、開始10分くらいで気付かれはじめ、最終的には高得点の写真ばかりになっていました・・・笑(詳細は後述)

「近さ」を考慮したスコア計算

以下が「近さ」を考慮したスコア計算例です。(スコアの幅は0点〜1000点にしています)

def calc_score(response, width):
    score = 0
    nosetip = 0
    
    count = 0
    if len(response.json()) > 2:
        count = 2
    else:
        count = len(response.json())
    
    for i in range(count):
        smile = response.json()[i]['faceAttributes']['smile']  # 笑顔度 0 ~ 1
        sadness = response.json()[i]['faceAttributes']['emotion']['sadness']  # 悲しみ 0 ~ 1
        score += (smile - sadness) * 100

        # 鼻端の近さ
        if nosetip == 0:
            nosetip = response.json()[i]['faceLandmarks']['noseTip']['x']
        else:
            nosetip -= response.json()[i]['faceLandmarks']['noseTip']['x']

    # 鼻端の距離スコアを計算
    nosetip = 1 - (abs(nosetip) / width)
    
    if score == 0: return 0
    
    return max(0, int(round(((score / 2) * nosetip), 1)*10))

# responseは前項で取得したFace APIのレスポンス、widthは写真の横幅
score = calc_score(response, width)

このようにすることで、隣り合う2人の距離(鼻端のx座標)が近ければ近いほど高得点が出るようにしました。

やってみてどうだったか

「笑顔」という条件を提示することで、いろんな人の様々な表情を垣間見ることができました!
と同時に、今回はリーダーボードがほぼリアルタイム*1で更新されたので、リーダーボードに映っている、スコアの高い写真を参考にどのように写真を取れば高得点が叩き出せるか試行錯誤するという一味違った体験を提供することができ、大盛況に終わることができたのではないかと思っています!

想定外だったことといえば、先にも述べた通り開始10分くらいでハックされたことです(汗)
今回はx座標のみスコアに寄与するため、横並びではなく、縦並びで写真を撮るとかなり高いスコアを叩き出すことができます。*2

これにより、忘年会後半はほぼ全員が縦並びで写真を撮るという見慣れない光景が広がっていました(笑)が、結果的に仲良さそうに写真を撮っていたので運営側としても大成功&嬉しかったです!

最後に

告知です!

2月6日にMeetUpを開催します!

ママ向けNo1アプリ「ママリ」を運営している社員とざっくばらんにお話ししてみませか?

当日はCEO、CTO、デザイナー、新規事業開発責任者、人事責任者など様々な職種の社員が参加します!ちょっと話聞きたいなくらいの温度感でOKなのでご参加お待ちしてます!
(忘年会の様子とか聞いてみたいな〜という方も気軽にお越しください(笑))

connehito.connpass.com

*1:1分間隔でスコア計算をしていた

*2:鼻端のx座標の差分を限りなく0にすることができるため