@aptpod/iscp-ts - v1.0.0

@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

アップストリームとダウンストリーム

アップストリームとダウンストリームのサンプルを示します。アップストリームで送信したデータポイントをダウンストリームで確認する簡単なサンプルです。

事前準備

本サンプルを動作させるために、必要なパッケージをインポートし、定数を定義します。

// 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 Call

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

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

詳細については以下を参照してください。

Version history

v1.0.0 (2024-04-16)

  • サポートする Node.js のバージョンを 18.12.0 以上にしました。
  • README に記載しているアップストリームを行うコードにおいて、BaseTime の Priority を 255 以下の数値で設定するように修正しました。

v0.12.1 (2024-03-19)

Bug Fixes

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が含まれていない不具合を修正しました。
  • WebTransportConnector を使用して iSCP に接続できない不具合を修正しました。

v0.9.0 (2022-11-08)

Features

  • ベータバージョン初回リリース。

Generated using TypeDoc