Skip to content

Instantly share code, notes, and snippets.

@3846masa

3846masa/_.md Secret

Created December 8, 2024 11:57
Show Gist options
  • Select an option

  • Save 3846masa/06733b7b8dd012074f9472521dd600a0 to your computer and use it in GitHub Desktop.

Select an option

Save 3846masa/06733b7b8dd012074f9472521dd600a0 to your computer and use it in GitHub Desktop.

whywaita Advent Calendar 2024 / 7 日目

Note

この記事は whywaita Advent Calendar 2024 7 日目 の記事でしたが、遅刻しました。

前回は neotaso さんでした。はじめまして。

次回は 抹茶(matchaism) さんの記事です。はじめまして。僕が投稿遅刻したのでもう出ています。

whywaita コミュニティのみなさん、はじめまして。 whywaita と同じ会社に勤めている 1994 年生まれの @3846masa です。

whywaita とは同期で、大学時代にお互い異なる所属サークルでしたが、なんか一緒に LT イベントしたとかで知り合いました。

普段ブログ書かないので、あんまりいいネタが思いつかなかったんですが、お手柔らかによろしくお願いします。

Chrome DevTools Protocol プロキシを作って、複数の Chromium をひとつの Chromium に見せかける

whywaita コミュニティのみなさんは、おそらくプログラマの方が多いんじゃないかなと感じてます。

プログラマのみなさんにはわかってもらえると思う1んですが、Chromium が積まれた謎デバイス2向け Web アプリデバッグって面倒じゃないですか。

Chromium が積まれた謎デバイスは、「Chrome DevTools Protocol のポートは 9222 です」とか用意してくれてて、そこに手元の Chrome から chrome://inspect すれば DevTools が開けます。

でも、謎デバイスを作っている人たちは、なぜかわからないんですが、ポート番号みんな好き勝手決めるので、どれがどれだか覚えられないわけです。

これが普段の業務で「P 社の端末だから 12345 で、T 社の端末だから 54321 で...」ってやってると、病みます。

あと、単純にデバイスが多いとデバイスごとの IP アドレスとかも忘れます。ブロードキャスト通信するとクビになる呪いをかけられているので、mDNS とかも使えないし。

というわけで、複数の Chrome DevTools Protocol をまとめて、1 台の Chrome のようにみせかけたら、IP アドレスもポート番号も 1 つだけ覚えればよくなって、幸せですよねという話をします。

Chrome DevTools Protocol

HTTP REST API と WebSockets API で組まれているプロトコルです。

あとは https://chromedevtools.github.io/devtools-protocol/ 読んでください。

Chrome っぽく振る舞う

ようは HTTP なので、適当に Node.js で HTTP サーバー立てて、CDP のリクエストが来たら適当に流せばよいです。

まずはブラウザになりすまします。

import fastify from 'fastify';

const app = fastify();

app.get('/json/version', (_, reply) => {
  reply.send({
    'Protocol-Version': '1.3',
    Browser: 'Chrome/999.9.9.9',
  });
});

app.get('/json/list', (_, reply) => {
  reply.send([
    {
      id: '8f76cf89-8a41-4b4a-b42c-b15f1003c902',
      type: 'page',
      url: 'https://example.com/',
      title: '存在しないタブ',
    },
  ]);
});

void app.listen({ host: '0.0.0.0', port: 9222 });

そして、Access local servers and Chrome instances with port forwarding  |  Chrome DevTools  |  Chrome for Developers の手順で Target discovery settings に登録します。ポートフォワーディングはしなくていいです。

すると、なんか出ます。

複数の CDP を 1 つの CDP にする

作戦としては、

  1. 複数デバイスのタブ情報から最初のタブだけ抽出して /json/list をつくる
    • サイネージなので、複数タブ開いてることはない前提とする
  2. /devtools/page/{id} を本物のデバイスにプロキシする

というわけで、適当にこんな感じのコードを書きます。

import fastify from 'fastify';
import proxy from '@fastify/http-proxy';

const app = fastify();

const DEVICES = [
  { name: 'Device 1', origin: 'http://device01.local:9222' },
  { name: 'Device 2', origin: 'http://device02.local:12345' },
  { name: 'Device 3', origin: 'http://device03.local:54321' },
];
const PAGE_ID_ORIGIN_MAP = new Map();

app.get('/json/list', async (_, reply) => {
  const list = await Promise.all(
    DEVICES.map((device) => {
      return fetch(`${device.origin}/json/list`)
        .then((r) => r.json())
        .then((pages) => [device, pages[0]]);
    }),
  );

  for (const [device, page] of list) {
    PAGE_ID_ORIGIN_MAP.set(page.id, device.origin);
  }

  reply.send(
    list.map(([d, p]) => ({ ...p, title: `${d.name} - ${p.title}` })),
  );
});

app.register(proxy, {
  prefix: '/devtools/page/:pageId',
  rewritePrefix: '/devtools/page/:pageId',
  upstream: void 0,
  wsUpstream: void 0,
  replyOptions: {
    getUpstream(req) {
      return PAGE_ID_ORIGIN_MAP.get(req.params.pageId);
    },
  },
  websocket: true,
});

void app.listen({ host: '0.0.0.0', port: 9222 });

/json/list にリクエストがきたときに、各デバイスの /json/list から先頭 1 つ目を集めて、Map に pageId とデバイスのエンドポイントをマッピングしておきます。

そして、@fastify/http-proxy を使って /devtools/page/:pageId にきた WebSocket を Map の情報をもとに転送します。

実際に試してみると、こんな感じになります。

https://i.imgur.com/yIhF5eG.mp4

おわり

いかがでしたか?みなさんの業務効率化に役立つと嬉しいですね。

Footnotes

  1. プログラマは大きく分けて「Chromium が積まれた謎デバイス上で動く Web アプリを作るひと」と「そうじゃないひと」にわけられます。僕の周りのチームメンバーはみんな前者だったので、おそらく世界も前者のプログラマがほとんどを占めていると言えます。

  2. Chromium が積まれた謎デバイスっていうのは、なんらかの OS を搭載していて、起動すると Chromium が立ち上がって画面全体を覆い尽くし、適当なページ表示させてサイネージとかキオスク端末みたいに使うやつです。イメージつかなそうであれば、「Chromium kiosk "Raspberry Pi"」とかで調べてください。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment