Slide 1

Slide 1 text

電話を切らさない技術 電話⾃動応答サービスを⽀える フロントエンド JSConf.jp 2024, 2024-11-23 佐川 善彦

Slide 2

Slide 2 text

⾃⼰紹介 佐川 善彦(さがわ よしひこ) Software Engineer クライミング‧DJ 趣味 2013 メーカーに新卒⼊社、産業機器を設計 2019 SWエンジニアに転向、受託開発 2023 IVRyに⼊社、Frontend主軸に⾃社開発 @barometrica

Slide 3

Slide 3 text

オフィスに壁とDJ機材、あります!

Slide 4

Slide 4 text

さて、突然ですが

Slide 5

Slide 5 text

ブラウザから電話をかけたことがある⼈ ✋

Slide 6

Slide 6 text

ブラウザから電話をかけたことがある⼈ ✋

Slide 7

Slide 7 text

今⽇はブラウザ通話を⽀える フロントエンドについてお話します

Slide 8

Slide 8 text

話すこと アーキテクチャ React / Next.js … 話さないこと PJマネジメント WebRTC …

Slide 9

Slide 9 text

⽬次 1. IVRy について 2. ブラウザ通話について 3. ブラウザ通話を⽀えるフロントエンド 4. IVRy のこれから

Slide 10

Slide 10 text

IVRy について

Slide 11

Slide 11 text

電話⾃動応答サービス IVRy ● ⽉額2,980円から電話の⾃動応答ルールを簡単に作成 ● 全ての電話業務を誰でもすぐにAIを使って効率化

Slide 12

Slide 12 text

IVRyが向き合う課題 電話はいまでも最重要連絡⼿段

Slide 13

Slide 13 text

IVRyが向き合う課題 電話を当たり前に取れない時代

Slide 14

Slide 14 text

IVRyが提供するサービス 電話業務を⾃動化‧効率化 ダイヤルプッシュと AI の対話をハイブリッドで設定

Slide 15

Slide 15 text

IVRyが提供するサービス 様々なサービスを提供していますが …

Slide 16

Slide 16 text

IVRyが提供するサービス 今⽇はブラウザ通話について話します …

Slide 17

Slide 17 text

ブラウザ通話について

Slide 18

Slide 18 text

ブラウザ通話とは ブラウザ上で電話の受発信を可能にするサービス ● 電話をかける発信、電話を受ける受信のいずれもブラウザで可能 ● インターネット回線を通じて電話をすることができる「IP電話」により 実現されるサービス ● 050番号のほか、市外局番(0ABJ番号)でも通話することが可能

Slide 19

Slide 19 text

IVRy でのシステム構成 Twilio と⾃社サーバを介して受発信

Slide 20

Slide 20 text

ブラウザ通話のフロー ブラウザで受電する場合

Slide 21

Slide 21 text

事前接続 受電時の フロー ブラウザ通話のフロー ブラウザで受電する場合

Slide 22

Slide 22 text

電話のプロダクトとして⼤事なこと 「電話はつながって当たり前」 を提供すること

Slide 23

Slide 23 text

電話のプロダクトとして⼤事なこと 「電話はつながって当たり前」 を提供すること ブラウザ通話もつながって当然 のはずなのですが‧‧‧ ↓

Slide 24

Slide 24 text

● 過去の通話履歴の参照など、通話中に画⾯操作したいケースは多い ブラウザ通話の失敗談 失敗談①:画⾯遷移で通話が切れる

Slide 25

Slide 25 text

ブラウザ通話の失敗談 失敗談②:店舗切り替えで通話が切れる ● IVRy は⼀つのアカウントで別の店舗に切り替えることができる ● 切替時に店舗情報を⼀度初期化する必要があるが、 このときに通話も切れてしまっていた

Slide 26

Slide 26 text

電話のプロダクトとして⼤事なこと 「ブラウザ通話もつながって当たり前」 を提供し続けるための⼯夫が必要

Slide 27

Slide 27 text

ブラウザ通話を⽀えるフロントエンド

Slide 28

Slide 28 text

ブラウザ通話を⽀えるフロントエンド:アーキテクチャ Next.js:Pages Router + SSG AWS S3 + CloudFront でホスティング‧配信 ‧Pages Router ‧SSG ‧ブラウザ通話可能なページは  いずれもSPA

Slide 29

Slide 29 text

ブラウザ−Twilio 間の接続を維持することが重要 ブラウザ通話をつながって当たり前にするには? ● うっかり切れてしまうことのないように、構造的に維持したい ○ → Layout Pattern を利⽤

Slide 30

Slide 30 text

ブラウザ通話を⽀えるフロントエンド:接続の維持 ● 複数ページにまたがる共通コンポーネントを Layout として切り出し ● ページごとに Layout 指定 ● 同じ Layout の画⾯に遷移する際、 共通コンポーネントを再レンダリングさせない Layout Pattern Header Page1 Header Page2 Header Page3

Slide 31

Slide 31 text

ブラウザ通話を⽀えるフロントエンド:接続の維持 export function Layout({ children }) { return ( <> {children} > ) } components/Layout.tsx export function Page1() { return ... } Page.getLayout = function getLayout(page) { return {page} } pages/page1.tsx Header Page1 Layout Layout Pattern

Slide 32

Slide 32 text

ブラウザ通話を⽀えるフロントエンド:接続の維持 ● Layout に切り出すことでページ操作による通話処理への影響を排除 ○ 各画⾯の開発時の通話処理への考慮事項も削減 ● ブラウザ通話の使⽤可否も Layout の呼び分けで対応可能に Layout に通話処理を切り出して接続状態を保持 Page1 Page2 Page3 CallConainer ‧通話処理 ‧接続保持 ‧... CallConainer ‧通話処理 ‧接続保持 ‧... CallConainer ‧通話処理 ‧接続保持 ‧...

Slide 33

Slide 33 text

ブラウザ通話を⽀えるフロントエンド:接続の維持 Page1 pages/page1.tsx components/LayoutForCall.tsx export function LayoutForCall({ children }) { return ( {children} ) } export function HogePage() { return ... } HogePage.getLayout = function getLayout(page) { return {page} } LayoutForCall CallConainer ‧通話処理 ‧接続保持 ‧... Layout に通話処理を切り出して接続状態を保持

Slide 34

Slide 34 text

● 複雑さの要因 ○ 外部サービス(Twilio)の SDK が React ⽤に作られていない ○ 保持すべき状態の数が多い ● 複雑さによるリスク ○ 何か不具合があった場合の影響範囲の特定が難化 ○ 開発の属⼈化 ○ 機能改善の速度低下 ブラウザ通話を⽀えるフロントエンド:メンテナンス 通話ロジックは複雑になりやすい 保守性を担保したい : 凝集性‧結合性を守りたい

Slide 35

Slide 35 text

ブラウザ通話を⽀えるフロントエンド:メンテナンス features ディレクトリに通話ロジックを集約 ● ただディレクトリに集約しただけでは意図しない形で使われる危険性あり ○ → Linter のカスタムルールで凝集性‧結合性を担保 . ├── src │ ├── features │ │ ├── call │ │ │ ├── index.ts │ │ │ ├── components │ │ │ │ ├── CallContainer │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── hooks.ts

Slide 36

Slide 36 text

ブラウザ通話を⽀えるフロントエンド:メンテナンス Linter で意図しない外部アクセスを制限 const generateFeatureAccessRules = () => fs .readdirSync(path.join(__dirname, 'src', 'features'), { withFileTypes: true, }) .filter((dirent) => dirent.isDirectory()) .flatMap((dirent) => [ { module: `src/features/${dirent.name}/**/*`, allowReferenceFrom: [], allowSameModule: true, }, { module: `src/features/${dirent.name}/index.[jt]s{,x}`, allowReferenceFrom: [''], }, ]) .eslintrc.js ● 指定したコンポーネントのみ利⽤できるようにして保守性担保 export { CallContainer } from './components/CallContainer' export { CallConfirmModal } from './components/CallConfirmModal' ... features/call/index.ts features ディレクトリ直下の index.ts で export したもののみ ディレクトリ外から参照可

Slide 37

Slide 37 text

IVRy のこれから

Slide 38

Slide 38 text

● 正式リリースから4年、プロダクト規模も開発陣も拡⼤の⼀途 ● エンジニアの数は3年で 2名から34名へ ● 導⼊業界は 94/99 業界に拡⼤ = 解決したい課題も拡⼤ ○ → これから①:フロントエンド基盤改善 ○ → これから②:新機能開発 IVRy の現状 IVRy は急拡⼤のまっただ中 ※⽇本標準産業分類(令和5年)の中分類99業種をもとに計測

Slide 39

Slide 39 text

IVRy のこれから ● リファクタリング ○ 歴史的経緯による「割れ窓」を直していきたい ○ 誰でもフロントエンドを触りやすい環境にしていきたい ● ⾃動テスト ○ ⼈⼒の QA に頼っている状況 ○ ブラウザ通話の振る舞いも⾃動でテストしていきたい ● パフォーマンス ○ 最近だとウェブフォント最適化とか ● アクセシビリティ ● デザインシステム ● … これから①:フロントエンド基盤改善

Slide 40

Slide 40 text

IVRy のこれから これから②:新機能開発 通話相⼿ ⼀次受け+保留 転送先 ● 例1: 保留転送 ○ さらに複雑化する通話ロジックを いかにシンプルに保つか? ○ 保留時のUXはどうあるべきか? ● 例2: FAX ○ 「FAXの当たり前」を ブラウザでどう提供するか? ○ 当たり前をどう超えていくか? FAX相⼿

Slide 41

Slide 41 text

まとめ ● ブラウザ通話で⼤事なこと ○ 「つながって当たり前」を提供すること ● ブラウザ通話を⽀えるフロントエンド ○ Layout に通話処理を切り出すことで 画⾯操作の影響なく接続を維持することが可能に ○ 複雑な通話ロジックを features ディレクトリに集約、 また Linter によるアクセス制御で構造的に保守性を担保 ● IVRy のこれから ○ 急拡⼤に伴い新機能開発‧基盤整備などやっていき!

Slide 42

Slide 42 text

【宣伝1】エンジニア忘年 LT 会、やります!

Slide 43

Slide 43 text

【宣伝2】企業ブース、出してます! AI ⾃動応答などぜひご体験ください!! ノベルティーもあります!! 3F 301 Track B

Slide 44

Slide 44 text

We are Hiring !!! 詳しい採⽤情報はこちら https://www.notion.so/ivry-jp/IVRy-b30395752c 7c4a448f1520576dc55778