Chrome拡張6モジュールでSF業務を効率化した話

ClassLab Engineering の Dev チームメンバーが執筆しました。

「Salesforceでの入力作業、なんとかならない?」——コールセンターのオペレーターから毎週のように聞こえてくる声でした。

ClassLabのコールセンターでは、オペレーターが通話しながらSalesforceに情報を入力します。通話中に複数のフィールドをコピーし、ダッシュボードを確認し、次の発信先を選ぶ——この繰り返しを1日数百回。数秒の効率化が、1日で数時間の差になります。

この記事では、Salesforce上のコールセンター業務を効率化するChrome拡張機能「CX Operations Support」を内製し、6つのモジュールを1つの拡張に統合した設計と実装を公開します。

目次

1. 背景 — なぜChrome拡張で解決したか

SalesforceのカスタマイズではなくChrome拡張を選んだ理由

Salesforce上の業務効率化なら、Lightning Web Components(LWC)やフローで対応するのが正道です。しかし私たちが直面していたのはSalesforceの外側の問題でした。

  • ダッシュボードの自動リフレッシュ: Salesforceのダッシュボードは手動更新が必要。リアルタイム表示にはAPIポーリングが必要だが、API上限の制約あり
  • 発信ロック制御: 特定条件下でオペレーターの発信を制限する仕組みが、Salesforce標準機能では実現困難
  • 複数フィールドのワンクリックコピー: Salesforceの標準UIでは1フィールドずつしかコピーできない
  • これらはブラウザ側でDOMを操作しないと解決できない課題です。Chrome拡張ならSalesforceの画面上に機能を重ね合わせ、標準UIを拡張できます。

    最初はバラバラだった

    当初は課題ごとに個別のChrome拡張を開発していました。ダッシュボード自動更新用、フィールドコピー用、発信ロック用——計5つの拡張が並行稼働。

    問題は管理コストです。5つの拡張を全オペレーターのPCにインストール・更新する運用が破綻しかけていました。バージョン不一致、拡張同士の競合、設定の分散——これを1つに統合するプロジェクトが始まりました。

    2. 課題 — Chrome拡張統合の4つの壁

    | # | 課題 | 難易度 | 詳細 |
    |—|——|——–|——|
    | 1 | Manifest V3移行 | 高 | 旧拡張はManifest V2。V2は2024年以降非推奨 |
    | 2 | モジュール間の設定管理 | 中 | 6モジュールが個別に設定を持つと混乱する |
    | 3 | Salesforce DOM の不安定さ | 高 | LightningのDOMは動的生成でセレクターが変わりやすい |
    | 4 | ヘルスチェック | 中 | 拡張が全オペレーターのPCで正しく動いているか監視 |

    特に課題1のManifest V3移行が技術的に最もインパクトが大きかった。V2のBackground Pages(常駐スクリプト)がV3ではService Worker(イベント駆動)に変わり、状態管理のモデルが根本的に変更されます。

    3. 設計 — モジュラーChrome拡張アーキテクチャ

    graph TD
        subgraph Extension["CX Operations Support"]
            BG[Service Worker<br/>background.js]
            POPUP[Popup UI<br/>設定画面]
            SETTINGS[Settings Manager<br/>chrome.storage]
        end
    
        subgraph Modules["6 Modules"]
            M1[dashboard-refresh<br/>ダッシュボード自動更新]
            M2[dial-lock<br/>発信ロック制御]
            M3[field-copier<br/>フィールド一括コピー]
            M4[off-phone-monitor<br/>離席モニタリング]
            M5[reaction-checker<br/>応答チェック]
            M6[staff-selector<br/>担当者選択UI]
        end
    
        subgraph External["External"]
            SF[Salesforce<br/>Lightning]
            GAS[Google Apps Script<br/>ヘルスチェック]
            SENTRY[Sentry<br/>エラー監視]
        end
    
        BG --> M1
        BG --> M2
        BG --> M3
        BG --> M4
        BG --> M5
        BG --> M6
        POPUP --> SETTINGS
        SETTINGS --> BG
        M1 --> SF
        M2 --> SF
        M3 --> SF
        BG --> GAS
        BG --> SENTRY
    
        style BG fill:#3b82f6,color:#fff
        style SETTINGS fill:#f59e0b,color:#fff
        style GAS fill:#10b981,color:#fff

    設計判断①: モジュール分離パターン

    6つの機能を1つの拡張に統合するにあたり、モジュール単位でディレクトリを分離する設計にしました。

    modules/
    ├── dashboard-refresh/
    │   └── content.js
    ├── dial-lock/
    │   ├── background.js
    │   ├── content.js
    │   └── injected.js
    ├── field-copier/
    │   ├── content.js
    │   └── styles.css
    ├── off-phone-monitor/
    │   ├── background.js
    │   ├── content.js
    │   └── injected.js
    ├── reaction-checker/
    │   └── background.js
    └── staff-selector/
        ├── background.js
        ├── content.js
        ├── injected.js
        └── injected.css

    各モジュールはcontent script(Salesforceページに注入)とbackground script(Service Workerで処理)の組み合わせ。モジュール間の依存はゼロにし、1つのモジュールを無効化しても他に影響しない設計です。

    設計判断②: Settings Managerによる一元管理

    6モジュールの設定(有効/無効、パラメータ)をchrome.storage.syncで一元管理。PopupのUIから全モジュールのON/OFFを切り替えられます。

    // Settings Manager: モジュール設定の一元管理
    class SettingsManager {
      static async getModuleSettings(moduleName) {
        const result = await chrome.storage.sync.get(moduleName);
        return result[moduleName] ?? { enabled: true };
      }
    
      static async setModuleSettings(moduleName, settings) {
        await chrome.storage.sync.set({ [moduleName]: settings });
      }
    }

    chrome.storage.syncを使うことで、オペレーターがPCを変えても設定が同期されます。

    設計判断③: Manifest V3対応

    | V2(旧) | V3(新) | 影響 |
    |———|———|——|
    | Background Pages(常駐) | Service Worker(イベント駆動) | 状態をメモリに保持できない → `chrome.storage`に永続化 |
    | `chrome.browserAction` | `chrome.action` | API名変更 |
    | Content Script から直接DOM操作 | `chrome.scripting.executeScript` | 動的注入が必要 |
    | XMLHttpRequest | Fetch API | 非同期処理モデル変更 |

    最大の変更はService Workerです。V2では常駐スクリプトが状態を保持できましたが、V3ではService Workerが一定時間で停止します。タイマー処理(ダッシュボード自動更新、離席モニタリング)はchrome.alarmsAPIに移行しました。

    // V2(旧): setInterval で常駐
    setInterval(() => refreshDashboard(), 5 * 60 * 1000);
    
    // V3(新): chrome.alarms でイベント駆動
    chrome.alarms.create('dashboard-refresh', { periodInMinutes: 5 });
    chrome.alarms.onAlarm.addListener((alarm) => {
      if (alarm.name === 'dashboard-refresh') {
        chrome.tabs.query({ url: '*://*.force.com/*' }, (tabs) => {
          tabs.forEach(tab => chrome.scripting.executeScript({
            target: { tabId: tab.id },
            func: () => document.querySelector('[title="更新"]')?.click(),
          }));
        });
      }
    });

    4. 実装 — 6モジュールの概要

    このプロジェクトはフロントエンドエンジニア1名が主担当で、Salesforceエンジニア1名がLightning DOM解析を支援する2名体制です。

    Module 1: dashboard-refresh(ダッシュボード自動更新)

    Salesforceダッシュボードの「更新」ボタンをContent Scriptで定期的にクリック。chrome.alarmsで5分間隔に設定。SV(スーパーバイザー)がリアルタイムでKPIを確認できるようになりました。

    Module 2: dial-lock(発信ロック制御)

    特定条件(研修中、休憩中、後処理中)でオペレーターの発信ボタンを非活性化。Salesforceの画面上にinjected.jsでオーバーレイを表示し、誤発信を防止。提携先ごとの設定ファイルで条件をカスタマイズ可能。

    Module 3: field-copier(フィールド一括コピー)

    顧客情報(氏名・住所・電話番号・契約番号)をワンクリックでクリップボードにコピー。Salesforceの標準UIでは1フィールドずつコピーする必要がありましたが、この拡張で1クリックで全フィールドをタブ区切りでコピー。オペレーターの入力工数を大幅に削減。

    Module 4: off-phone-monitor(離席モニタリング)

    オペレーターのステータス(通話中/待機中/離席中)を監視し、長時間離席をSVに通知。Background Service WorkerからGASにheartbeatを送信し、GAS側で集計。

    Module 5: reaction-checker(応答チェック)

    着信後の応答時間を計測し、基準値(3秒)を超えた場合にアラート表示。

    Module 6: staff-selector(担当者選択UI)

    通話を転送する際の担当者選択UIを拡張。Salesforceの標準転送UIより高速に操作できるカスタムドロップダウンをinjected.jsで注入。

    失敗: SalesforceのDOM変更で全モジュール壊れた

    最大の失敗は、SalesforceのLightning UIアップデートでDOMセレクターが一斉に変わったこと。特にfield-copierがフィールド値を取得するセレクターが全滅し、1日中全オペレーターがコピー機能を使えない状態に。

    対策として、セレクターを設定ファイルに外出しし、DOMが変わった場合にコードを変更せず設定ファイルの更新だけで対応できるようにしました。さらにSentryでエラーを即座に検出し、セレクター失敗のアラートがSlackに飛ぶ仕組みを追加。この改善後は、DOM変更の検出→セレクター修正→Chrome Web Store更新で平均2時間以内に復旧できるようになりました。

    5. 結果 — オペレーター業務の定量改善

    30名のオペレーターに展開後、1ヶ月間の計測データです。

    | 指標 | Before | After | 改善率 |
    |——|——–|——-|——–|
    | フィールドコピー操作時間/件 | 15秒(5フィールド×3秒) | 1秒(ワンクリック) | **-93%** |
    | ダッシュボード更新忘れによるKPI遅延 | 週3-5回 | 0回 | **ゼロ達成** |
    | 誤発信件数/月 | 平均8件 | 平均1件(拡張未インストールのゲスト端末から) | **-88%** |
    | 拡張のバージョン不一致問題 | 月2-3回 | 0回(1拡張に統合) | **ゼロ達成** |
    | 拡張管理工数(IT部門) | 5拡張×月2時間 | 1拡張×月1時間 | **-90%** |

    1件15秒の価値

    「15秒→1秒」は小さく見えますが、オペレーター30名が1日平均80件コピー操作を行います。

    • Before: 30名 × 80件 × 15秒 = 10時間/日
    • After: 30名 × 80件 × 1秒 = 40分/日
    • field-copier 1つだけで1日あたり9時間以上の工数削減

      6. 展望 — 次に取り組むこと

      GASヘルスチェックの強化

      現在は拡張からGASにheartbeatを送信して死活監視していますが、モジュール単位の稼働状況をダッシュボード化し、「どのオペレーターのどのモジュールが停止しているか」を一目で把握できるようにする計画です。

      設定のリモート配信

      現在はchrome.storage.syncで設定を管理していますが、IT部門がWeb管理画面から全オペレーターの設定を一括変更できる仕組みを構築中です。新しいモジュールの有効化や、提携先固有の設定変更をオペレーターに触らせずに適用できます。

      Chrome拡張の開発はフロントエンドの知識で始められますが、Manifest V3のService Worker制約、SalesforceのDOM不安定性、30名規模への展開という実運用の課題は、作ってみないとわかりませんでした。

      社内ツールの内製に興味がある方、ぜひお話ししましょう。

      採用情報

      ClassLab では一緒に技術的挑戦に取り組むエンジニアを募集しています。

  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次