iSCP Client for TypeScript は、iSCP version 2 を用いたリアルタイム API にアクセスするためのクライアントライブラリです。
npm でインストールする場合は以下を実行します。
npm install @aptpod/iscp-ts
yarn でインストールする場合は以下を実行します。
yarn add @aptpod/iscp-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 サーバーへ送信しています。
// 指定したミリ秒の時間だけ待機します。
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
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')
}
アップストリームのコードと、ダウンストリームのコードを並列で実行します。
;(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
E2E コールのサンプルを示します。コントローラノードが対象ノードに対して指示を出し、対象ノードは受信完了のリプライを行う簡単なサンプルです。
本サンプルを動作させるために、必要なパッケージをインポートし、定数を定義します。
// 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
}
コントローラノードからメッセージを送信するサンプルです。このサンプルでは文字列メッセージを対象ノードに対して送信し、対象ノードからのリプライを待ちます。
// 指定したミリ秒の時間だけ待機します。
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')
}
コントローラノードからのコールを受け付け、すぐにリプライするサンプルです。
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')
}
;(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
当 SDK をブラウザと 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
詳細については以下を参照してください。
-
API リファレンス
- 過去のバージョンのリファレンスは こちら
- npm
- サポートする Node.js のバージョンに 22 を追加しました。
- Downstream 処理において、DownstreamChunk を取得する際に、参照メモリが開放されない不具合を修正しました。
- Upstream を開く際に、FlushPolicy に intervalOnly、bufferSizeOnly、intervalOrBufferSize を指定した場合に、参照メモリが開放されない不具合を修正しました。
- サポートする Node.js のバージョンを 18.12.0 以上にしました。
- README に記載しているアップストリームを行うコードにおいて、BaseTime の Priority を 255 以下の数値で設定するように修正しました。
- ブラウザ、または Node.js のストリーム API が web-streams-polyfill で置換されないように修正しました。
- Node.js で実行する場合に、環境変数を使用して Proxy Server を設定する機能を追加しました。
- Node.js v14 のサポートを終了しました。
- Upstream で Ack を全て受信済みの状態で Close した時に、Close Timeout の時間が経過するまで Close の処理が完了しない不具合を修正しました。
- Conn に Reconnecting、Reconnected イベントを追加しました。
- ISCPFailedMessageError に resultCode、resultString のプロパティを追加しました。
- DownstreamConfig、Downstream クラスに omitEmptyChunk のプロパティを追加しました。
- Conn.connect で autoReconnect を指定した時に再接続が正常に動作しない不具合を修正しました。
- Upstream の機能を追加しました。
- E2E Call の機能を追加しました。
- WebSocketConnector の enableTLS のデフォルトを true に変更しました。
- Conn クラスにデフォルト値の static プロパティを追加しました。
- EventListenerOptions から timeout, onTimeout のプロパティを削除しました。(Braking Change)
- README に「アップストリームとダウンストリーム」のサンプルを追加しました。
- README に「E2E Call」のサンプルを追加しました。
- API Document の Index の Category を変更しました。
- Downstream で例外が発生した時に Close されない不具合を修正しました。
- Disconnect メッセージを受信した時に再接続しないように修正しました。
- README にアクセストークンを取得するサンプルを追加しました。
- README に Version history を追加しました。
- 依存パッケージの指定にweb-streams-polyfillが含まれていない不具合を修正しました。
- WebTransportConnector を使用して iSCP に接続できない不具合を修正しました。
- ベータバージョン初回リリース。