0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザで「Isolated Web Apps」+「Direct Sockets API」により TCP・UDP を直接扱う【Web:3】

Posted at

(この記事は Web Advent Calendar 2025 の記事【3つ目】です)

はじめに

この記事は、ブラウザで TCP・UDP の通信を直接扱うことができる API の「Direct Sockets API」に関する話です。今回、Direct Sockets API を使った通信を試してみます。

実際に試していく

「Direct Sockets API」を使うための下準備

「Direct Sockets API」を使うためには、通常の Webページで処理を実行するのではなく、以下の記事に書いた「Isolated Web Apps」を組み合わせる等の対応が必要です(※ 別の方法もあるようですが)。

●ブラウザで「Isolated Web Apps」を試す(Direct Sockets API を使うための下準備) - Qiita
 https://qiita.com/youtoy/items/650fec647f348ec15862

2つの下準備

まずは、上記の記事で書いた「Isolated Web Apps の動作確認」のところまで進めます。そして、上記の記事で作っていたアプリを書きかえます。

それと TCP・UDP の通信相手は、以下の別の記事で書いた Node.js のコードを用います。

●Node.js の標準機能でシンプルな TCP と UDP - Qiita
 https://qiita.com/youtoy/items/c429d0376a10bbd0b985

これらが準備できたら、Isolated Web Apps + Direct Sockets API を使った TCP・UDP の通信を試していきます。

「Direct Sockets API」を扱うための実装

Isolated Web Apps で Direct Sockets API を扱うための実装・設定を以下としました。

HTMLファイルの実装

以下は HTMLファイルです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>IWA Direct Sockets テスト</title>
    <style>
      body {
        font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
        margin: 16px;
      }
      section {
        border: 1px solid #ccc;
        padding: 12px;
        margin-bottom: 16px;
      }
      button {
        padding: 4px 10px;
        margin-bottom: 8px;
      }
      pre {
        background: #f7f7f7;
        padding: 8px;
        min-height: 3em;
        white-space: pre-wrap;
      }
    </style>
  </head>
  <body>
    <h1>IWA Direct Sockets テスト</h1>
    <p id="env"></p>

    <section>
      <h2>TCP クライアント (127.0.0.1:5000)</h2>
      <button id="tcp-btn">TCP: 1回送信して返信を受け取る</button>
      <pre id="tcp-log"></pre>
    </section>

    <section>
      <h2>UDP クライアント (127.0.0.1:5001)</h2>
      <button id="udp-btn">UDP: 1回送信して返信を受け取る</button>
      <pre id="udp-log"></pre>
    </section>

    <script type="module" src="/app.js"></script>
  </body>
</html>

ページを開いた時の見た目は、以下となります。

2025-12-14_23-49-02.jpg

JavaScriptファイルの実装

以下は JavaScriptファイルの実装です。

const envEl = document.getElementById("env");
const tcpLog = document.getElementById("tcp-log");
const udpLog = document.getElementById("udp-log");

function logTcp(msg) {
  tcpLog.textContent += msg + "\n";
}

function logUdp(msg) {
  udpLog.textContent += msg + "\n";
}

envEl.textContent =
  `TCPSocket: ${typeof TCPSocket !== "undefined"} / ` +
  `UDPSocket: ${typeof UDPSocket !== "undefined"}`;

document.getElementById("tcp-btn").addEventListener("click", () => {
  runTcpOnce().catch((err) => {
    logTcp("エラー: " + err);
  });
});

async function runTcpOnce() {
  if (typeof TCPSocket === "undefined") {
    logTcp("TCPSocket が利用できません(IWA 以外 or フラグ不足?)");
    return;
  }

  const host = "127.0.0.1";
  const port = 5001;
  const message = "IWAからのTCP通信です\n";

  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  logTcp(`接続中: ${host}:${port} ...`);

  const socket = new TCPSocket(host, port);

  let openedInfo;
  try {
    openedInfo = await socket.opened;
  } catch (e) {
    logTcp("接続失敗: " + e);
    return;
  }

  const { readable, writable, localAddress, localPort } = openedInfo;
  logTcp(`接続完了: local=${localAddress}:${localPort}`);

  const writer = writable.getWriter();
  await writer.ready;
  await writer.write(encoder.encode(message));
  writer.releaseLock();
  logTcp(`送信: ${message.trimEnd()}`);

  const reader = readable.getReader();
  const { value, done } = await reader.read();
  if (done) {
    logTcp("受信なしのままストリーム終了");
  } else {
    const text = decoder.decode(value);
    logTcp("受信: " + text.trimEnd());
  }
  reader.releaseLock();

  await socket.close();
  logTcp("ソケットをクローズしました");
}

document.getElementById("udp-btn").addEventListener("click", () => {
  runUdpOnce().catch((err) => {
    logUdp("エラー: " + err);
  });
});

async function runUdpOnce() {
  if (typeof UDPSocket === "undefined") {
    logUdp("UDPSocket が利用できません(IWA 以外 or フラグ不足?)");
    return;
  }

  const host = "127.0.0.1";
  const port = 5002;
  const message = "IWAからのUDP通信です\n";

  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  logUdp(`UDP ソケット接続中 (connected モード): ${host}:${port} ...`);

  const udpSocket = new UDPSocket({ remoteAddress: host, remotePort: port });

  let openedInfo;
  try {
    openedInfo = await udpSocket.opened;
  } catch (e) {
    logUdp("UDP ソケットオープン失敗: " + e);
    return;
  }

  const { readable, writable, localAddress, localPort } = openedInfo;
  logUdp(`UDP ソケットオープン: local=${localAddress}:${localPort}`);

  const writer = writable.getWriter();
  await writer.ready;
  await writer.write({
    data: encoder.encode(message),
  });
  writer.releaseLock();
  logUdp(`送信: ${message}`);

  const reader = readable.getReader();
  const { value, done } = await reader.read();
  if (done || !value) {
    logUdp("受信なしのままストリーム終了");
  } else {
    const { data } = value;
    const text = decoder.decode(data);
    logUdp("受信: " + text.trimEnd());
  }
  reader.releaseLock();

  await udpSocket.close();
  logUdp("UDP ソケットをクローズしました");
}

マニフェストファイルの内容

前回使った内容に関して、上記以外にマニフェストファイルの修正も行いました。内容は以下のとおりです。

{
  "name": "IWA TCP/UDP",
  "version": "1.0.0",
  "start_url": "/",

  "permissions_policy": {
    "cross-origin-isolated": ["self"],
    "direct-sockets": ["self"],
    "direct-sockets-private": ["self"]
  },

  "icons": [
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

今回の修正をするにあたり、以下のリポジトリにあるファイルを参照しました。

●GoogleChromeLabs/telnet-client
 https://github.com/GoogleChromeLabs/telnet-client?tab=readme-ov-file

参照したのは、以下のファイルです。

●telnet-client/assets/.well-known/manifest.webmanifest at main · GoogleChromeLabs/telnet-client
 https://github.com/GoogleChromeLabs/telnet-client/blob/main/assets/.well-known/manifest.webmanifest

今回の Direct Sockets API を扱うために必要になるっぽい、以下の "permissions_policy" の部分を追加しました。

2025-12-15_00-09-04.jpg

アプリのインストール・実行関連

前回の記事で試したアプリをアンインストールして、今回のアプリをインストールします。

それについて、 chrome://web-app-internals でアンインストールをしてから、今回のアプリをインストールしました。

以下が、インストールや起動、処理の実行に関する内容です。

インストール・起動コマンド

前回と同じコマンドで、アプリのインストールと起動を行います。具体的には、以下を実行します。
※ この時、あらかじめ前回の記事と同様に、ローカルサーバーを起動しておいてください

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --user-data-dir=/tmp/iwa-profile \
  --enable-features=IsolatedWebApps,IsolatedWebAppDevMode \
  --install-isolated-web-app-from-url=http://127.0.0.1:5500/

一度インストールした後は、再度アプリを起動する時には以下のコマンドで OK です。

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --user-data-dir=/tmp/iwa-profile \
  --enable-features=IsolatedWebApps,IsolatedWebAppDevMode

処理の実行

ブラウザが開いたら、 chrome://apps/ のページへ移動して自作の Isolated Web Apps を開きます。

その後、Node.js で実装した通信相手のサーバーを起動した状態にして(※ 前に書いた記事内の TCP、UDP のどちらも対応できるものを準備しました)、その上で今回のアプリのページ内で用意したボタンを押してみます。

その結果、以下のように TCP と UDP のどちらの通信も行うことができました。

2025-12-14_23-59-35.jpg

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?