UNPKG

@aptpod/iscp-ts

Version:

iSCP 2.0 client library for TypeScript

598 lines (458 loc) 20.4 kB
# \@aptpod/iscp-ts > iSCP Client for TypeScript は、iSCP version 2 を用いたリアルタイム API にアクセスするためのクライアントライブラリです。 ## Installation npm でインストールする場合は以下を実行します。 ``` npm install @aptpod/iscp-ts ``` yarn でインストールする場合は以下を実行します。 ``` yarn add @aptpod/iscp-ts ``` ## Example - [アップストリームとダウンストリーム](#アップストリームとダウンストリーム) - [E2E Call](#e2e-call) ### アップストリームとダウンストリーム アップストリームとダウンストリームのサンプルを示します。アップストリームで送信したデータポイントをダウンストリームで確認する簡単なサンプルです。 #### 事前準備 本サンプルを動作させるために、必要なパッケージをインポートし、定数を定義します。 ```ts // iscp-tsをインポートします。 import * as iscp from '@aptpod/iscp-ts' // intdash APIのRESTクライアントを参照します。 // RESTクライアントを生成する手順については、 intdash API/SDK サイトの説明を参照してください。 import { Configuration, BrokerISCPApi } from './intdash' // デバッグ情報を文字列に変換するため、Node.jsのinspectを使用します。 import { inspect } from 'util' // iSCPのサーバーアドレス。 const ISCP_ADDRESS = 'localhost:8080' // WebSocket接続時にTLSを有効にします。 const WEBSOCKET_ENABLE_TLS = true // REST APIのBASE PATH。 const INTDASH_REST_API_BASE_PATH = 'https://localhost:8080/api' // アップストリームを行うノードのID。 const UPSTREAM_SOURCE_NODE_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' // REST APIを使ってiSCP接続用のアクセストークンを取得する関数を定義します。 const tokenSource = async () => { const configuration = new Configuration({ basePath: INTDASH_REST_API_BASE_PATH, }) const api = new BrokerISCPApi(configuration) const response = await api.issueISCPTicket() return response.data.ticket } ``` #### アップストリームを行うコードの定義 アップストリームを行うコードのサンプルです。このサンプルでは、基準時刻のメタデータと、文字列型のデータポイントを iSCP サーバーへ送信しています。 ```ts // 指定したミリ秒の時間だけ待機します。 const sleepMs = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)) // 現在の時刻をナノ秒で取得します。 const getNowTimeNano = () => BigInt(Date.now()) * BigInt(1000_000) const startUpstream = async () => { // WebSocketのコネクターを使用します。 const connector = new iscp.WebSocketConnector({ enableTLS: WEBSOCKET_ENABLE_TLS, }) // iSCP接続を開始します。 const conn = await iscp.Conn.connect({ address: ISCP_ADDRESS, connector, tokenSource, nodeId: UPSTREAM_SOURCE_NODE_ID, }) // アップストリームを開きます。 const upstream = await conn.openUpstream({ sessionId: 'sessionId', }) // 基準時刻を送信します。 const start = getNowTimeNano() await sleepMs(1000) await conn.sendBaseTime( new iscp.BaseTime({ name: 'manual', elapsedTime: 0n, baseTime: start, priority: 60, sessionId: 'sessionId', }), ) // 一定時間ごとにデータポイントをアップストリームに書き込みます。 for (let i = 0; i < 4; i++) { await sleepMs(1000) await upstream.writeDataPoints(new iscp.DataId({ name: 'greeting', type: 'string' }), [ new iscp.DataPoint({ elapsedTime: getNowTimeNano() - start, payload: new TextEncoder().encode(`hello: ${i}`), }), ]) } // 終了を通知するデータポイントをアップストリームに書き込みます。 await upstream.writeDataPoints(new iscp.DataId({ name: 'end', type: 'string' }), [ new iscp.DataPoint({ elapsedTime: getNowTimeNano() - start, payload: new TextEncoder().encode('end'), }), ]) // 未送信のアップストリームのデータポイントを全て送信します。 await upstream.flush() // アップストリームを閉じます。 await upstream.close() console.log('[startUpstream]', 'closed upstream') // iSCPを切断します。 await conn.close() console.log('[startUpstream]', 'closed connection') } ``` #### ダウンストリームを行うコードの定義 前述のアップストリームで送信されたデータをダウンストリームで受信するコードのサンプルです。 `downstream.ts` ```ts const DEBUG_LOG_NEW_LINE_SEPARATOR = '\n===================================' const startDownstream = async () => { // WebSocketのコネクターを使用します。 const connector = new iscp.WebSocketConnector({ enableTLS: WEBSOCKET_ENABLE_TLS, }) // iSCP接続を開始します。 const conn = await iscp.Conn.connect({ address: ISCP_ADDRESS, connector, tokenSource, }) // ダウンストリームを開きます。 const downstream = await conn.openDownstream({ filters: [iscp.DownstreamFilter.allFor(UPSTREAM_SOURCE_NODE_ID)], }) // DownstreamMetadataを受信します。 downstream.addEventListener(iscp.Downstream.EVENT.METADATA, (metadata) => { if (metadata.metadata instanceof iscp.BaseTime) { console.log( '[startDownstream]', 'Received BaseTime:', inspect(metadata, { depth: Infinity }), DEBUG_LOG_NEW_LINE_SEPARATOR, ) } }) // DownstreamChunkを受信します。 downstream.addEventListener(iscp.Downstream.EVENT.CHUNK, (chunk) => { console.log( '[startDownstream]', 'Received DownstreamChunk', inspect(chunk, { depth: Infinity }), DEBUG_LOG_NEW_LINE_SEPARATOR, ) for (const dataPointGroup of chunk.dataPointGroups) { if (dataPointGroup.dataId.name === 'end') { console.log('[startDownstream]', 'Received end message', DEBUG_LOG_NEW_LINE_SEPARATOR) downstream.close() } } }) await downstream.waitClosed() console.log('[startDownstream]', 'closed downstream') await conn.close() console.log('[startDownstream]', 'closed connection') } ``` #### 実行 アップストリームのコードと、ダウンストリームのコードを並列で実行します。 ```ts ;(async () => { await Promise.all([startDownstream(), startUpstream()]) })() ``` 出力結果 ``` [startDownstream] Received BaseTime: DownstreamMetadata { sourceNodeId: '9fe734e5-2d43-437f-8331-838b1e6c1055', metadata: BaseTime3 { sessionId: 'sessionId', name: 'manual', priority: 1000, elapsedTime: 0n, baseTime: 1673936224024000000n } } =================================== [startDownstream] Received DownstreamChunk DownstreamChunk { upstreamInfo: UpstreamInfo { sessionId: 'sessionId', streamId: '9af44d42-b8d4-4cf0-91ec-b959818d80ad', sourceNodeId: '9fe734e5-2d43-437f-8331-838b1e6c1055' }, sequenceNumber: 1, dataPointGroups: [ DataPointGroup { dataId: DataId3 { name: 'greeting', type: 'string' }, dataPoints: [ DataPoint3 { elapsedTime: 2021000000n, payload: Uint8Array(8) [ 104, 101, 108, 108, 111, 58, 32, 48 ] }, DataPoint3 { elapsedTime: 3024000000n, payload: Uint8Array(8) [ 104, 101, 108, 108, 111, 58, 32, 49 ] } ] } ] } =================================== [startDownstream] Received DownstreamChunk DownstreamChunk { upstreamInfo: UpstreamInfo { sessionId: 'sessionId', streamId: '9af44d42-b8d4-4cf0-91ec-b959818d80ad', sourceNodeId: '9fe734e5-2d43-437f-8331-838b1e6c1055' }, sequenceNumber: 2, dataPointGroups: [ DataPointGroup { dataId: DataId3 { name: 'greeting', type: 'string' }, dataPoints: [ DataPoint3 { elapsedTime: 4026000000n, payload: Uint8Array(8) [ 104, 101, 108, 108, 111, 58, 32, 50 ] }, DataPoint3 { elapsedTime: 5027000000n, payload: Uint8Array(8) [ 104, 101, 108, 108, 111, 58, 32, 51 ] } ] }, DataPointGroup { dataId: DataId3 { name: 'end', type: 'string' }, dataPoints: [ DataPoint3 { elapsedTime: 5027000000n, payload: Uint8Array(3) [ 101, 110, 100 ] } ] } ] } =================================== [startDownstream] Received end message =================================== [startUpstream] closed upstream [startUpstream] closed connection [startDownstream] closed downstream [startDownstream] closed connection ``` </details> ### E2E Call E2E コールのサンプルを示します。コントローラノードが対象ノードに対して指示を出し、対象ノードは受信完了のリプライを行う簡単なサンプルです。 #### 事前準備 本サンプルを動作させるために、必要なパッケージをインポートし、定数を定義します。 ```ts // iscp-tsをインポートします。 import * as iscp from '@aptpod/iscp-ts' // intdash APIのRESTクライアントを参照します。 // RESTクライアントを生成する手順については、 intdash API/SDK サイトの説明を参照してください。 import { Configuration, BrokerISCPApi } from './intdash' // デバッグ情報を文字列に変換するため、Node.jsのinspectを使用します。 import { inspect } from 'util' // iSCPのサーバーアドレス。 const ISCP_ADDRESS = 'localhost:8080' // WebSocket接続時にTLSを有効にします。 const WEBSOCKET_ENABLE_TLS = true // REST APIのBASE PATH。 const INTDASH_REST_API_BASE_PATH = 'https://localhost:8080/api' // コントローラーのノードID。 const CONTROLLER_NODE_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' // コントール対象のノードID。 const TARGET_NODE_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' // REST APIを使ってiSCP接続用のアクセストークンを取得する関数を定義します。 const tokenSource = async () => { const configuration = new Configuration({ basePath: INTDASH_REST_API_BASE_PATH, }) const api = new BrokerISCPApi(configuration) const response = await api.issueISCPTicket() return response.data.ticket } ``` #### コントローラノードの定義 コントローラノードからメッセージを送信するサンプルです。このサンプルでは文字列メッセージを対象ノードに対して送信し、対象ノードからのリプライを待ちます。 ```ts // 指定したミリ秒の時間だけ待機します。 const sleepMs = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)) export const send = async () => { // WebSocketのコネクターを使用します。 const connector = new iscp.WebSocketConnector({ enableTLS: WEBSOCKET_ENABLE_TLS, }) // iSCP接続を開始します。 const conn = await iscp.Conn.connect({ address: ISCP_ADDRESS, connector, tokenSource, nodeId: CONTROLLER_NODE_ID, }) // 対象ノードが起動するまで少し待機します。 await sleepMs(1000) // Callを送信し、replayCallを受信するまで待機します。 const got = await conn.sendCallAndWaitReplyCall( new iscp.UpstreamCall({ destinationNodeId: TARGET_NODE_ID, name: 'greeting', type: 'string', payload: new TextEncoder().encode('hello'), }), ) console.log('[send]', 'Received replay call:', inspect(got, { depth: Infinity })) await conn.close() console.log('[send]', 'closed connection') } ``` #### 対象ノードのコード定義 コントローラノードからのコールを受け付け、すぐにリプライするサンプルです。 ```ts const reply = async () => { // WebSocketのコネクターを使用します。 const connector = new iscp.WebSocketConnector({ enableTLS: WEBSOCKET_ENABLE_TLS, }) // iSCP接続を開始します。 const conn = await iscp.Conn.connect({ address: ISCP_ADDRESS, connector, tokenSource, nodeId: TARGET_NODE_ID, }) // DownstreamCallの受信を監視し、受信したらすぐにreplyCallを送信します。 conn.addEventListener(iscp.Conn.EVENT.CALL, (call) => { console.log('[reply]', 'Received call:', inspect(call, { depth: Infinity })) conn .sendReplyCall( new iscp.UpstreamReplyCall({ requestCallId: call.callId, destinationNodeId: call.sourceNodeId, name: 'reply_greeting', type: 'string', payload: new TextEncoder().encode('world'), }), ) .finally(() => { // replayCallを送信したらiSCPを切断します。 conn.close() }) }) await conn.waitClosed() console.log('[reply]', 'closed connection') } ``` #### 実行 ```ts ;(async () => { await Promise.all([send(), reply()]) })() ``` 実行結果 ``` [reply] Received call: DownstreamCall { callId: '57ea53d2-f65b-4552-8367-4db83069241c', sourceNodeId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', name: 'greeting', type: 'string', payload: Uint8Array(5) [ 104, 101, 108, 108, 111 ] } [reply] closed connection [send] Received replay call: DownstreamReplyCall { requestCalId: '57ea53d2-f65b-4552-8367-4db83069241c', sourceNodeId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', name: 'reply_greeting', type: 'string', payload: Uint8Array(5) [ 119, 111, 114, 108, 100 ] } [send] closed connection ``` ## Proxy Server 当 SDK をブラウザと Node.js のどちらで実行するかによって設定方法が異なります。 ### ブラウザで実行する場合 各ブラウザのプロキシの設定方法を参照ください。 ### Node.js で実行する場合 以下環境変数を設定することで Proxy Server を経由して iSCP サーバーに接続することができます。 環境変数は大文字、または小文字で指定可能です。全ての環境変数を必ず指定する必要はありません。ご利用の環境に応じて、必要な環境変数を設定してください。 | 環境変数 | 説明 | 設定例 | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | `HTTP_PROXY` または `http_proxy` | iscp.WebSocketConnector で enableTLS を false で設定したときに使用する Proxy Server の URL です。 | `http://proxy.example.com`、または `https://proxy.example.com:3128` | | `HTTPS_PROXY` または `https_proxy` | iscp.WebSocketConnector で enableTLS を true で設定したときに使用する Proxy Server の URL です。 | `http://proxy.example.com`、または `https://proxy.example.com:3128` | | `NO_PROXY` または `no_proxy` | ホスト名をカンマ(,)区切りのリストで指定します。iscp.Conn.connect で指定する address (iSCP サーバーのアドレス)がいずれかに一致する場合、 Proxy Server を使用せずに直接 iSCP サーバーと通信します。  | `no-proxy.example.com,*.example.com` | Proxy Server にユーザー認証が必要な場合、以下のように設定してください。 - `HTTP_PROXY=http://username:password@proxy.example.com` - `HTTPS_PROXY=http://username:password@proxy.example.com` ## References 詳細については以下を参照してください。 - [API リファレンス](https://docs.intdash.jp/api/intdash-sdk/typescript/latest) - 過去のバージョンのリファレンスは [こちら](https://docs.intdash.jp/api/intdash-sdk/typescript-versions) - [npm](https://www.npmjs.com/package/@aptpod/iscp-ts) ## Version history ### v1.1.1 (2025-02-18) #### Security - 依存パッケージ [`elliptic`](https://www.npmjs.com/package/elliptic) を削除し、[脆弱性](https://www.npmjs.com/advisories/1102309) への対策を実施しました。 ### v1.1.0 (2024-10-17) #### Features - サポートする Node.js のバージョンに 22 を追加しました。 #### Bug Fixes - Downstream 処理において、DownstreamChunk を取得する際に、参照メモリが開放されない不具合を修正しました。 - Upstream を開く際に、FlushPolicy に intervalOnly、bufferSizeOnly、intervalOrBufferSize を指定した場合に、参照メモリが開放されない不具合を修正しました。 ### v1.0.0 (2024-04-16) #### Security - サポートする Node.js のバージョンを 18.12.0 以上にしました。 #### Documentation - README に記載しているアップストリームを行うコードにおいて、BaseTime の Priority を 255 以下の数値で設定するように修正しました。 ### v0.12.1 (2024-03-19) #### Bug Fixes - ブラウザ、または Node.js の[ストリーム API](https://developer.mozilla.org/ja/docs/Web/API/Streams_API) が [web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill) で置換されないように修正しました。 ### v0.12.0 (2024-03-13) #### Features - Node.js で実行する場合に、環境変数を使用して Proxy Server を設定する機能を追加しました。 ### v0.11.1 (2023-05-23) #### Security - Node.js v14 のサポートを終了しました。 #### Bug Fixes - Upstream で Ack を全て受信済みの状態で Close した時に、Close Timeout の時間が経過するまで Close の処理が完了しない不具合を修正しました。 ### v0.11.0 (2023-04-13) #### Features - Conn に Reconnecting、Reconnected イベントを追加しました。 - ISCPFailedMessageError に resultCode、resultString のプロパティを追加しました。 - DownstreamConfig、Downstream クラスに omitEmptyChunk のプロパティを追加しました。 #### Bug Fixes - Conn.connect で autoReconnect を指定した時に再接続が正常に動作しない不具合を修正しました。 ### v0.10.0 (2023-01-27) #### Features - Upstream の機能を追加しました。 - E2E Call の機能を追加しました。 - WebSocketConnector の enableTLS のデフォルトを true に変更しました。 - Conn クラスにデフォルト値の static プロパティを追加しました。 - EventListenerOptions から timeout, onTimeout のプロパティを削除しました。(Braking Change) - README に「アップストリームとダウンストリーム」のサンプルを追加しました。 - README に「E2E Call」のサンプルを追加しました。 - API Document の Index の Category を変更しました。 #### Bug Fixes - Downstream で例外が発生した時に Close されない不具合を修正しました。 - Disconnect メッセージを受信した時に再接続しないように修正しました。 ### v0.9.1 (2022-11-17) #### Features - README にアクセストークンを取得するサンプルを追加しました。 - README に Version history を追加しました。 #### Bug Fixes - 依存パッケージの指定に[web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill)が含まれていない不具合を修正しました。 - WebTransportConnector を使用して iSCP に接続できない不具合を修正しました。 ### v0.9.0 (2022-11-08) #### Features - ベータバージョン初回リリース。