1.0.0 • Published 20 days ago

@aptpod/iscp-ts v1.0.0

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
20 days ago

\@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_proxyiscp.WebSocketConnector で enableTLS を false で設定したときに使用する Proxy Server の URL です。http://proxy.example.com、または https://proxy.example.com:3128
HTTPS_PROXY または https_proxyiscp.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

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

20 days ago

1.0.0-beta.0

20 days ago

0.12.1

2 months ago

0.12.1-beta.0

2 months ago

0.12.0-beta.3

2 months ago

0.12.0-beta.2

2 months ago

0.12.0

2 months ago

0.12.0-beta.1

2 months ago

0.12.0-beta.0

2 months ago

0.11.1-beta.0

12 months ago

0.11.0

1 year ago

0.11.1

12 months ago

0.10.1-beta.4

1 year ago

0.10.1-beta.2

1 year ago

0.10.1-beta.3

1 year ago

0.10.1-beta.0

1 year ago

0.10.1-beta.1

1 year ago

0.10.0

1 year ago

0.10.0-beta.2

1 year ago

0.10.0-beta.0

1 year ago

0.10.0-beta.1

1 year ago

0.9.1

1 year ago

0.9.1-beta.2

1 year ago

0.9.1-beta.1

1 year ago

0.9.0

1 year ago

0.5.0-beta.1

1 year ago

0.4.1

2 years ago

0.4.0

2 years ago

0.3.0

2 years ago

0.2.3

2 years ago

0.2.2

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.0

2 years ago