diff --git a/.gitignore b/.gitignore index 9c00a2d8..79dd1fab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ node_modules npm-debug.log -wasm.wasm -packages/e2ee/dist -packages/e2ee/_worker apidoc/ .log/ dist/ diff --git a/CHANGES.md b/CHANGES.md index c7fcbe39..87c4f1eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ ## develop - [UPDATE] メッセージングの compress で fflate を利用せず decompressionStream API を利用するようにする - - @voluntas +- [CHANGE] E2EE 機能を削除する ### misc diff --git a/NOTICE.md b/NOTICE.md deleted file mode 100644 index 6a1273b2..00000000 --- a/NOTICE.md +++ /dev/null @@ -1,33 +0,0 @@ -## https://golang.org/ - -- https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js - -``` -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` diff --git a/README.md b/README.md index d54c0b9c..9e9f6bde 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,6 @@ $ pnpm exec playwright install chromium --with-deps $ pnpm run e2e-test ``` -## E2EE (End to End Encryption) について - -詳細については以下をご確認ください。 - -[WebRTC SFU Sora 向け E2EE ライブラリ](https://github.com/shiguredo/sora-e2ee) - ## マルチトラックについて [WebRTC SFU Sora](https://sora.shiguredo.jp) は 1 メディアストリームにつき 1 音声トラック、 diff --git a/examples/e2ee/index.html b/examples/e2ee/index.html deleted file mode 100644 index 35d6d8ac..00000000 --- a/examples/e2ee/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - E2EE test - - - -
-

E2EE test

-

-
-
-

local

- -
-
-
- - -

remote

-
-
-
-
- - - - - \ No newline at end of file diff --git a/examples/e2ee/main.mts b/examples/e2ee/main.mts deleted file mode 100644 index 30cef756..00000000 --- a/examples/e2ee/main.mts +++ /dev/null @@ -1,167 +0,0 @@ -import Sora, { - type SoraConnection, - type SignalingNotifyMessage, - type ConnectionPublisher, -} from 'sora-js-sdk' - -document.addEventListener('DOMContentLoaded', async () => { - const SORA_SIGNALING_URL = import.meta.env.VITE_SORA_SIGNALING_URL - const SORA_CHANNEL_ID_PREFIX = import.meta.env.VITE_SORA_CHANNEL_ID_PREFIX || '' - const SORA_CHANNEL_ID_SUFFIX = import.meta.env.VITE_SORA_CHANNEL_ID_SUFFIX || '' - const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN || '' - - await Sora.initE2EE('https://sora-e2ee-wasm.shiguredo.app/2020.2/wasm.wasm').catch((e) => { - const errorMessageElement = document.querySelector('#error-message') - if (errorMessageElement) { - errorMessageElement.textContent = 'E2EE用 wasm ファイルの読み込みに失敗しました' - } - }) - - const client = new SoraClient( - SORA_SIGNALING_URL, - SORA_CHANNEL_ID_PREFIX, - SORA_CHANNEL_ID_SUFFIX, - ACCESS_TOKEN, - ) - - document.querySelector('#start')?.addEventListener('click', async () => { - const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) - await client.connect(stream) - }) - document.querySelector('#stop')?.addEventListener('click', async () => { - await client.disconnect() - }) -}) - -class SoraClient { - private debug = false - - private channelId: string - private metadata: { access_token: string } - private options: object - - private sora: SoraConnection - private connection: ConnectionPublisher - - constructor( - signalingUrl: string, - channelIdPrefix: string, - channelIdSuffix: string, - accessToken: string, - ) { - this.sora = Sora.connection(signalingUrl, this.debug) - this.channelId = `${channelIdPrefix}e2ee${channelIdSuffix}` - this.metadata = { access_token: accessToken } - this.options = { - e2ee: true, - } - - this.connection = this.sora.sendrecv(this.channelId, this.metadata, this.options) - - this.connection.on('notify', this.onnotify.bind(this)) - this.connection.on('track', this.ontrack.bind(this)) - this.connection.on('removetrack', this.onremovetrack.bind(this)) - } - - async connect(stream: MediaStream) { - await this.connection.connect(stream) - const localVideo = document.querySelector('#local-video') - if (localVideo) { - localVideo.srcObject = stream - } - } - - async disconnect() { - await this.connection.disconnect() - - // お掃除 - const localVideo = document.querySelector('#local-video') - if (localVideo) { - localVideo.srcObject = null - } - - const localConnectionId = document.querySelector('#local-connection-id') - if (localConnectionId) { - localConnectionId.textContent = '' - } - - const remoteVideos = document.querySelector('#remote-videos') - if (remoteVideos) { - remoteVideos.innerHTML = '' - } - } - - private onnotify(event: SignalingNotifyMessage): void { - if ( - event.event_type === 'connection.created' && - event.connection_id === this.connection.connectionId - ) { - const localConnectionId = document.querySelector('#local-connection-id') - if (localConnectionId) { - localConnectionId.textContent = this.connection.connectionId - } - - const localFingerprint = document.querySelector('#local-fingerprint') - if (localFingerprint) { - localFingerprint.textContent = this.connection.e2eeSelfFingerprint || null - } - } - - if (event.event_type === 'connection.created') { - const remoteFingerprints = this.connection.e2eeRemoteFingerprints || {} - Object.keys(remoteFingerprints).filter((connectionId) => { - const fingerprintElement = document.querySelector( - `#remote-video-box-${connectionId}-fingerprint`, - ) - if (fingerprintElement) { - fingerprintElement.textContent = `fingerprint: ${remoteFingerprints[connectionId]}` - } - }) - } - } - - private ontrack(event: RTCTrackEvent): void { - const stream = event.streams[0] - /* -
-
connectionId: ${stream.id} -
fingerprint: ${stream.id} - -
- */ - - const remoteVideoBoxId = `remote-video-box-${stream.id}` - const remoteVideos = document.querySelector('#remote-videos') - if (remoteVideos && !remoteVideos.querySelector(`#${remoteVideoBoxId}`)) { - //
を作る - const remoteVideoBox = document.createElement('div') - remoteVideoBox.id = remoteVideoBoxId - //
を作る - const connectionIdElement = document.createElement('div') - connectionIdElement.id = `${remoteVideoBoxId}-connection-id` - connectionIdElement.textContent = `connectionId: ${stream.id}` - remoteVideoBox.appendChild(connectionIdElement) - //
を作る - const fingerprintElement = document.createElement('div') - fingerprintElement.id = `${remoteVideoBoxId}-fingerprint` - remoteVideoBox.appendChild(fingerprintElement) - //
diff --git a/examples/package.json b/examples/package.json index 65d90b9e..97f6e2a5 100644 --- a/examples/package.json +++ b/examples/package.json @@ -8,4 +8,4 @@ "dependencies": { "sora-js-sdk": "workspace:*" } -} \ No newline at end of file +} diff --git a/package.json b/package.json index a51a5855..816bc9eb 100644 --- a/package.json +++ b/package.json @@ -35,13 +35,13 @@ "@playwright/test": "1.46.1", "typedoc": "0.26.6", "typescript": "5.5.4", - "vite": "5.4.2", + "vite": "5.4.3", "vitest": "2.0.5" }, "resolutions": { "get-pkg-repo": "5.0.0" }, - "packageManager": "pnpm@9.7.1", + "packageManager": "pnpm@9.9.0", "engines": { "node": ">=18" } diff --git a/packages/e2ee/package.json b/packages/e2ee/package.json deleted file mode 100644 index e851c63e..00000000 --- a/packages/e2ee/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@sora/e2ee", - "version": "2021.1.0", - "description": "WebRTC SFU Sora JavaScript E2EE Library", - "author": "Shiguredo Inc.", - "license": "Apache-2.0", - "main": "dist/sora_e2ee.mjs", - "module": "dist/sora_e2ee.mjs", - "types": "dist/sora_e2ee.d.ts", - "scripts": { - "build": "pnpm run build:worker && pnpm run build:development", - "build:development": "NODE_ENV=development rollup -c rollup.config.mjs --bundleConfigAsCjs", - "build:worker": "tsc --project tsconfig.worker.json", - "lint": "biome lint ./src", - "fmt": "biome format --write ./src", - "check": "echo \"no check\"", - "test": "echo \"no test\"" - }, - "devDependencies": { - "@rollup/plugin-node-resolve": "15.2.3", - "@rollup/plugin-replace": "5.0.7", - "@rollup/plugin-typescript": "11.1.6", - "rollup": "4.21.2", - "tslib": "2.7.0" - }, - "dependencies": { - "@sora/go-wasm": "workspace:^" - } -} \ No newline at end of file diff --git a/packages/e2ee/rollup.config.mjs b/packages/e2ee/rollup.config.mjs deleted file mode 100644 index 541f8092..00000000 --- a/packages/e2ee/rollup.config.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import fs from 'node:fs' -import resolve from '@rollup/plugin-node-resolve' -import replace from '@rollup/plugin-replace' -import typescript from '@rollup/plugin-typescript' -import pkg from './package.json' - -const banner = `/** - * ${pkg.name} - * ${pkg.description} - * @version: ${pkg.version} - * @author: ${pkg.author} - * @license: ${pkg.license} - **/ -` -const workerScript = fs.readFileSync('./_worker/sora_e2ee_worker.js', 'base64') - -export default [ - { - input: 'src/sora_e2ee.ts', - plugins: [ - replace({ - __SORA_E2EE_VERSION__: pkg.version, - __WORKER_SCRIPT__: workerScript, - preventAssignment: true, - }), - resolve(), - typescript({ - tsconfig: './tsconfig.json', - }), - ], - output: { - sourcemap: false, - file: 'dist/sora_e2ee.mjs', - format: 'module', - name: 'SoraE2EE', - banner: banner, - }, - }, -] diff --git a/packages/e2ee/src/sora_e2ee.ts b/packages/e2ee/src/sora_e2ee.ts deleted file mode 100644 index 818cf9ea..00000000 --- a/packages/e2ee/src/sora_e2ee.ts +++ /dev/null @@ -1,270 +0,0 @@ -import WasmExec from '@sora/go-wasm' - -type PreKeyBundle = { - identityKey: string - signedPreKey: string - preKeySignature: string -} - -type RemoteSecretKeyMaterial = { - keyId: number - secretKeyMaterial: Uint8Array -} - -type ReceiveMessageResult = { - remoteSecretKeyMaterials: Record - messages: Uint8Array[] -} - -type StartResult = { - selfKeyId: number - selfSecretKeyMaterial: Uint8Array -} - -type StartSessionResult = { - selfConnectionId: string - selfKeyId: number - selfSecretKeyMaterial: Uint8Array - remoteSecretKeyMaterials: Record - messages: Uint8Array[] -} - -type StopSessionResult = { - selfConnectionId: string - selfKeyId: number - selfSecretKeyMaterial: Uint8Array - messages: Uint8Array[] -} - -interface E2EE { - init(): Promise<{ preKeyBundle: PreKeyBundle }> - addPreKeyBundle( - connectionId: string, - identityKey: string, - signedPreKey: string, - preKeySignature: string, - ): Error | null - receiveMessage(message: Uint8Array): [ReceiveMessageResult, Error | null] - remoteFingerprints(): Record - selfFingerprint(): string - start(selfConnectionId: string): [StartResult, Error | null] - startSession( - connectionId: string, - identityKey: string, - signedPreKey: string, - preKeySignature: string, - ): [StartSessionResult, Error | null] - stopSession(connectionId: string): [StopSessionResult, Error | null] - version(): string -} - -declare class Go { - importObject: { - go: Record void> - } - run(instance: unknown): Promise -} - -interface E2EEWindow extends Window { - Go: Go - e2ee: E2EE -} -declare let window: E2EEWindow - -const WORKER_SCRIPT = '__WORKER_SCRIPT__' - -class SoraE2EE { - worker: Worker | null - // biome-ignore lint/suspicious/noExplicitAny: - onWorkerDisconnect: (() => any) | null - - constructor() { - // 対応しているかどうかの判断 - // @ts-ignore トライアル段階の API なので無視する - const supportsInsertableStreams = !!RTCRtpSender.prototype.createEncodedStreams - if (!supportsInsertableStreams) { - throw new Error('E2EE is not supported in this browser.') - } - this.worker = null - this.onWorkerDisconnect = null - } - // worker を起動する - startWorker(): void { - // ワーカーを起動する - const workerScript = atob(WORKER_SCRIPT) - this.worker = new Worker( - URL.createObjectURL(new Blob([workerScript], { type: 'application/javascript' })), - ) - this.worker.onmessage = (event): void => { - const { operation } = event.data - if (operation === 'disconnect' && typeof this.onWorkerDisconnect === 'function') { - this.onWorkerDisconnect() - } - } - } - // worker の掃除をする - clearWorker(): void { - if (this.worker) { - this.worker.postMessage({ - type: 'clear', - }) - } - } - // worker を終了する - terminateWorker(): void { - if (this.worker) { - this.worker.terminate() - } - } - // 初期化処理 - async init(): Promise { - const { preKeyBundle } = await window.e2ee.init() - return preKeyBundle - } - - setupSenderTransform(readableStream: ReadableStream, writableStream: WritableStream): void { - if (!this.worker) { - throw new Error('Worker is null. Call startWorker in advance.') - } - const message = { - type: 'encrypt', - readableStream: readableStream, - writableStream: writableStream, - } - this.worker.postMessage(message, [readableStream, writableStream]) - } - - setupReceiverTransform(readableStream: ReadableStream, writableStream: WritableStream): void { - if (!this.worker) { - throw new Error('Worker is null. Call startWorker in advance.') - } - const message = { - type: 'decrypt', - readableStream: readableStream, - writableStream: writableStream, - } - this.worker.postMessage(message, [readableStream, writableStream]) - } - - postRemoteSecretKeyMaterials(result: ReceiveMessageResult): void { - if (!this.worker) { - throw new Error('Worker is null. Call startWorker in advance.') - } - this.worker.postMessage({ - type: 'remoteSecretKeyMaterials', - remoteSecretKeyMaterials: result.remoteSecretKeyMaterials, - }) - } - - postRemoveRemoteDeriveKey(connectionId: string): void { - if (!this.worker) { - throw new Error('Worker is null. Call startWorker in advance.') - } - this.worker.postMessage({ - type: 'removeRemoteDeriveKey', - connectionId: connectionId, - }) - } - - postSelfSecretKeyMaterial( - selfConnectionId: string, - selfKeyId: number, - selfSecretKeyMaterial: Uint8Array, - waitingTime = 0, - ): void { - if (!this.worker) { - throw new Error('Worker is null. Call startWorker in advance.') - } - this.worker.postMessage({ - type: 'selfSecretKeyMaterial', - selfConnectionId: selfConnectionId, - selfKeyId: selfKeyId, - selfSecretKeyMaterial: selfSecretKeyMaterial, - waitingTime: waitingTime, - }) - } - - startSession(connectionId: string, preKeyBundle: PreKeyBundle): StartSessionResult { - const [result, err] = window.e2ee.startSession( - connectionId, - preKeyBundle.identityKey, - preKeyBundle.signedPreKey, - preKeyBundle.preKeySignature, - ) - if (err) { - throw err - } - return result - } - - stopSession(connectionId: string): StopSessionResult { - const [result, err] = window.e2ee.stopSession(connectionId) - if (err) { - throw err - } - return result - } - - receiveMessage(message: Uint8Array): ReceiveMessageResult { - const [result, err] = window.e2ee.receiveMessage(message) - if (err) { - throw err - } - return result - } - - start(selfConnectionId: string): StartResult { - const [result, err] = window.e2ee.start(selfConnectionId) - if (err) { - throw err - } - return result - } - - addPreKeyBundle(connectionId: string, preKeyBundle: PreKeyBundle): void { - const err = window.e2ee.addPreKeyBundle( - connectionId, - preKeyBundle.identityKey, - preKeyBundle.signedPreKey, - preKeyBundle.preKeySignature, - ) - if (err) { - throw err - } - } - - selfFingerprint(): string { - return window.e2ee.selfFingerprint() - } - - remoteFingerprints(): Record { - return window.e2ee.remoteFingerprints() - } - - static async loadWasm(wasmUrl: string): Promise { - if (!window.e2ee === undefined) { - console.warn('E2ee wasm is already loaded. Will not be reload.') - return - } - WasmExec() - if (!window.Go) { - throw new Error(`Failed to load module Go. window.Go is ${window.Go}.`) - } - const go = new Go() - const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), go.importObject) - go.run(instance) - if (!window.e2ee) { - throw new Error(`Failed to load module e2ee. window.e2ee is ${window.e2ee}.`) - } - } - - static version(): string { - return '__SORA_E2EE_VERSION__' - } - - static wasmVersion(): string { - return window.e2ee.version() - } -} - -export default SoraE2EE diff --git a/packages/e2ee/src/worker/e2ee.ts b/packages/e2ee/src/worker/e2ee.ts deleted file mode 100644 index 00512411..00000000 --- a/packages/e2ee/src/worker/e2ee.ts +++ /dev/null @@ -1,322 +0,0 @@ -/// - -type UnencryptedBytes = { - key: 10 - delta: 3 - undefined: 1 -} - -type Chunk = { - synchronizationSource: number - data: ArrayBuffer - type: keyof UnencryptedBytes -} -// TODO: 扱う数値が大きい箇所では Number から BigInt に置き換える -// TODO: BigInt に置き換える際に変更する -const maxKeyId = 2 ** 32 -const maxCount = 2 ** 32 - -const selfDeriveKeyMap: Map = - new Map() -const countMap: Map = new Map() -const writeIVMap: Map = new Map() -const remoteDeriveKeyMap: Map> = new Map() -const latestRemoteKeyIdMap: Map = new Map() - -const littleEndian = true -const bigEndian = !littleEndian - -const textEncoder = new TextEncoder() -const textDecoder = new TextDecoder() - -// VP8 のみ -// TODO(nakai): VP9 / AV1 も将来的に対応も考える -const unencryptedBytes = { - // I フレーム - key: 10, - // 非 I フレーム - delta: 3, - // オーディオ - undefined: 1, -} - -function getCount(connectionId: string): number { - return countMap.get(connectionId) || 0 -} - -function setCount(connectionId: string, count: number): Map { - return countMap.set(connectionId, count) -} - -function getRemoteDeriveKey(connectionId: string, keyId: number): CryptoKey | undefined { - if (!remoteDeriveKeyMap.has(connectionId)) { - throw new Error('REMOTE-DERIVEKEY-MAP-NOT-FOUND') - } - - const deriveKeyMap = remoteDeriveKeyMap.get(connectionId) - if (!deriveKeyMap) { - return - } - return deriveKeyMap.get(keyId) -} - -function setRemoteDeriveKey(connectionId: string, keyId: number, deriveKey: CryptoKey): void { - let deriveKeyMap = remoteDeriveKeyMap.get(connectionId) - if (!deriveKeyMap) { - deriveKeyMap = new Map() - } - deriveKeyMap.set(keyId, deriveKey) - remoteDeriveKeyMap.set(connectionId, deriveKeyMap) -} - -function setLatestRemoteKeyId(connectionId: string, keyId: number): void { - const latestRemoteKeyId = latestRemoteKeyIdMap.get(connectionId) - if (latestRemoteKeyId) { - if (latestRemoteKeyId < keyId) { - latestRemoteKeyIdMap.set(connectionId, keyId) - } - } else { - latestRemoteKeyIdMap.set(connectionId, keyId) - } -} - -function removeOldRemoteDeriveKeys(): void { - latestRemoteKeyIdMap.forEach((latestKeyId, connectionId) => { - const deriveKeyMap = remoteDeriveKeyMap.get(connectionId) - if (deriveKeyMap) { - deriveKeyMap.forEach((_, keyId) => { - if (latestKeyId !== keyId) { - deriveKeyMap.delete(keyId) - } - }) - } - }) -} - -function removeDeriveKey(connectionId: string): void { - latestRemoteKeyIdMap.delete(connectionId) - remoteDeriveKeyMap.delete(connectionId) -} - -function getLatestSelfDeriveKey(): { - connectionId: string - keyId: number - deriveKey: CryptoKey -} { - const deriveKey = selfDeriveKeyMap.get('latest') - if (!deriveKey) { - throw new Error('LATEST-SELF-DERIVEKEY-NOT_FOUND') - } - return deriveKey -} - -function setSelfDeriveKey(connectionId: string, keyId: number, deriveKey: CryptoKey): void { - const currentSelfDeriveKey = selfDeriveKeyMap.get('latest') - if (currentSelfDeriveKey) { - if (currentSelfDeriveKey.keyId < keyId) { - const nextSelfDeriveKey = { connectionId, keyId, deriveKey } - selfDeriveKeyMap.set('latest', nextSelfDeriveKey) - } - } else { - const nextSelfDeriveKey = { connectionId, keyId, deriveKey } - selfDeriveKeyMap.set('latest', nextSelfDeriveKey) - } -} - -function silenceFrame(encodedFrame: Chunk): Chunk { - // connection.created, receiveMessage 受信前の場合 - if (encodedFrame.type === undefined) { - // 音声は暗号化はいると聞けたものじゃないので置き換える - const newData = new ArrayBuffer(3) - const newUint8 = new Uint8Array(newData) - - // Opus サイレンスフレーム - newUint8.set([0xd8, 0xff, 0xfe]) - encodedFrame.data = newData - } else { - // 映像が正常じゃないため PLI ストームが発生してしまう - // そのため 320x240 の真っ黒な画面に置き換える - const newData = new ArrayBuffer(60) - const newUint8 = new Uint8Array(newData) - - newUint8.set([ - 0xb0, 0x05, 0x00, 0x9d, 0x01, 0x2a, 0xa0, 0x00, 0x5a, 0x00, 0x39, 0x03, 0x00, 0x00, 0x1c, - 0x22, 0x16, 0x16, 0x22, 0x66, 0x12, 0x20, 0x04, 0x90, 0x40, 0x00, 0xc5, 0x01, 0xe0, 0x7c, - 0x4d, 0x2f, 0xfa, 0xdd, 0x4d, 0xa5, 0x7f, 0x89, 0xa5, 0xff, 0x5b, 0xa9, 0xb4, 0xaf, 0xf1, - 0x34, 0xbf, 0xeb, 0x75, 0x36, 0x95, 0xfe, 0x26, 0x96, 0x60, 0xfe, 0xff, 0xba, 0xff, 0x40, - ]) - encodedFrame.data = newData - } - return encodedFrame -} - -function setWriteIV(connectionId: string, keyId: number, writeIV: Uint8Array): void { - const key = [connectionId, keyId.toString()].join(':') - writeIVMap.set(key, writeIV) -} - -function getWriteIV(connectionId: string, keyId: number): Uint8Array | undefined { - const key = [connectionId, keyId.toString()].join(':') - return writeIVMap.get(key) -} - -function generateIV(count: number, connectionId: string, keyId: number): Uint8Array { - // TODO: keyId が Number.MAX_SAFE_INTEGER, 7 byte を超えていた場合はエラーか例外 - // TODO: count が Number.MAX_SAFE_INTEGER, 7 byte を超えていた場合はエラーか例外 - // 32 bit まで - if (maxKeyId < keyId || maxCount < count) { - throw new Error('EXCEEDED-MAXIMUM-BROADCASTING-TIME') - } - - const writeIV = getWriteIV(connectionId, keyId) - if (!writeIV) { - throw new Error('WRITEIV-NOT-FOUND') - } - - const paddingLength = Nn - Uint32Array.BYTES_PER_ELEMENT - - const countWithPaddingBuffer = new ArrayBuffer(Nn) - const countWithPaddingDataView = new DataView(countWithPaddingBuffer) - - countWithPaddingDataView.setUint32(paddingLength, count, bigEndian) - - const iv = new Uint8Array(Nn) - const countWithPadding = new Uint8Array(countWithPaddingBuffer) - for (let i = 0; i < Nn; i++) { - iv[i] = writeIV[i] ^ countWithPadding[i] - } - - return iv -} - -function parsePayload( - payloadType: keyof UnencryptedBytes, - payload: Chunk['data'], -): [Uint8Array, Uint8Array] { - return [ - new Uint8Array(payload, 0, unencryptedBytes[payloadType]), - new Uint8Array(payload, unencryptedBytes[payloadType]), - ] -} - -function encodeFrameAdd( - header: Uint8Array, - sframeHeader: Uint8Array, - connectionId: string, -): Uint8Array { - const connectionIdData = textEncoder.encode(connectionId) - const frameAdd = new Uint8Array( - header.byteLength + sframeHeader.byteLength + connectionIdData.byteLength, - ) - frameAdd.set(header, 0) - frameAdd.set(sframeHeader, header.byteLength) - frameAdd.set(connectionIdData, header.byteLength + sframeHeader.byteLength) - return frameAdd -} - -async function encryptFunction( - encodedFrame: Chunk, - controller: TransformStreamDefaultController, -): Promise { - const { connectionId, keyId, deriveKey } = getLatestSelfDeriveKey() - if (!deriveKey) { - return - } - - const currentCount = getCount(connectionId) - - // count が 32 bit 以上の場合は停止する - if (currentCount > maxCount) { - postMessage({ type: 'disconnect' }) - } - - const iv = generateIV(currentCount, connectionId, keyId) - if (!iv) { - return - } - const [header, payload] = parsePayload(encodedFrame.type, encodedFrame.data) - const sframeHeader = encodeSFrameHeader(0, currentCount, keyId) - const frameAdd = encodeFrameAdd(header, sframeHeader, connectionId) - - crypto.subtle - .encrypt( - { - name: 'AES-GCM', - iv: iv, - // 暗号化されていない部分 - additionalData: frameAdd, - }, - deriveKey, - payload, - ) - .then((cipherText) => { - const newData = new ArrayBuffer(frameAdd.byteLength + cipherText.byteLength) - const newDataUint8 = new Uint8Array(newData) - newDataUint8.set(frameAdd, 0) - newDataUint8.set(new Uint8Array(cipherText), frameAdd.byteLength) - encodedFrame.data = newData - - controller.enqueue(encodedFrame) - }) - - setCount(connectionId, currentCount + 1) -} - -async function decryptFunction( - encodedFrame: Chunk, - controller: TransformStreamDefaultController, -): Promise { - // 空フレーム対応 - if (encodedFrame.data.byteLength < 1) { - return - } - - try { - const frameMetadataBuffer = encodedFrame.data.slice(0, unencryptedBytes[encodedFrame.type]) - const frameMetadata = new Uint8Array(frameMetadataBuffer) - const [sframeHeaderBuffer, connectionIdBuffer, encryptedFrameBuffer] = splitHeader( - encodedFrame.data.slice(unencryptedBytes[encodedFrame.type]), - ) - const sframeHeader = new Uint8Array(sframeHeaderBuffer) - const connectionId = textDecoder.decode(connectionIdBuffer) - const [s, count, keyId] = parseSFrameHeader(sframeHeaderBuffer) - // 今回は s flag は 0 のみ - if (s !== 0) { - throw new Error('UNEXPECTED-S-FLAG') - } - - const deriveKey = getRemoteDeriveKey(connectionId, keyId) - if (!deriveKey) { - return - } - - const iv = generateIV(count, connectionId, keyId) - if (!iv) { - return - } - - const frameAdd = encodeFrameAdd(frameMetadata, sframeHeader, connectionId) - - crypto.subtle - .decrypt( - { - name: 'AES-GCM', - iv: iv, - additionalData: frameAdd, - }, - deriveKey, - new Uint8Array(encryptedFrameBuffer), - ) - .then((plainText) => { - const newData = new ArrayBuffer(frameMetadataBuffer.byteLength + plainText.byteLength) - const newUint8 = new Uint8Array(newData) - newUint8.set(new Uint8Array(frameMetadataBuffer, 0, unencryptedBytes[encodedFrame.type])) - newUint8.set(new Uint8Array(plainText), unencryptedBytes[encodedFrame.type]) - encodedFrame.data = newData - controller.enqueue(encodedFrame) - }) - } catch (e) { - // 想定外のパケットフォーマットを受信した場合 - controller.enqueue(silenceFrame(encodedFrame)) - } -} diff --git a/packages/e2ee/src/worker/index.ts b/packages/e2ee/src/worker/index.ts deleted file mode 100644 index 52f939af..00000000 --- a/packages/e2ee/src/worker/index.ts +++ /dev/null @@ -1,143 +0,0 @@ -/// - -type RemoteSecretKeyMaterial = { - keyId: number - secretKeyMaterial: Uint8Array -} - -// nonce サイズ -const Nn = 12 -// key サイズ -const Nk = 16 -// key サイズ(bit) -const keyLength = Nk * 8 - -async function generateDeriveKey(material: CryptoKey): Promise { - const salt = textEncoder.encode('SFrame10') - const info = textEncoder.encode('key') - const deriveKey = await crypto.subtle.deriveKey( - { - name: 'HKDF', - salt: salt, - hash: 'SHA-256', - info: info, - }, - material, - { - name: 'AES-GCM', - length: keyLength, - }, - false, - ['encrypt', 'decrypt'], - ) - return deriveKey -} - -async function generateWriteIV(material: CryptoKey): Promise { - const salt = textEncoder.encode('SFrame10') - const info = textEncoder.encode('salt') - - const writeIVBuffer = await crypto.subtle.deriveBits( - { - name: 'HKDF', - salt: salt, - hash: 'SHA-384', - info: info, - }, - material, - // IV は 96 ビットなので - Nn * 8, - ) - const writeIV = new Uint8Array(writeIVBuffer) - return writeIV -} - -let removalTimeoutId = 0 - -// biome-ignore lint/suspicious/noGlobalAssign: 検討 -onmessage = (event): void => { - const { type } = event.data - if (type === 'selfSecretKeyMaterial') { - const { selfSecretKeyMaterial, selfConnectionId, selfKeyId, waitingTime } = event.data - const timeoutId = setTimeout(() => { - crypto.subtle - .importKey('raw', selfSecretKeyMaterial.buffer, 'HKDF', false, ['deriveBits', 'deriveKey']) - .then((material) => { - generateDeriveKey(material).then((deriveKey) => { - setSelfDeriveKey(selfConnectionId, selfKeyId, deriveKey) - }) - - generateWriteIV(material).then((writeIV) => { - setWriteIV(selfConnectionId, selfKeyId, writeIV) - }) - - clearTimeout(timeoutId) - }) - }, waitingTime || 0) - - // TODO: +1000 で鍵生成後に実行されるようにしているが短い場合は伸ばす - const removalWaitingTime = (waitingTime || 0) + 1000 - if (removalTimeoutId) { - // 動作済みタイマー有り - if (waitingTime) { - // connection.destroyed - clearTimeout(removalTimeoutId) - removalTimeoutId = setTimeout(() => { - removeOldRemoteDeriveKeys() - clearTimeout(removalTimeoutId) - removalTimeoutId = 0 - }, removalWaitingTime) - } - } else { - // 動作済みタイマーなし - // connection.created の場合も少し実行を遅らせる - removalTimeoutId = setTimeout(() => { - removeOldRemoteDeriveKeys() - clearTimeout(removalTimeoutId) - removalTimeoutId = 0 - }, removalWaitingTime) - } - } else if (type === 'remoteSecretKeyMaterials') { - const { remoteSecretKeyMaterials } = event.data - - for (const [connectionId, remoteSecretKeyMaterial] of Object.entries( - remoteSecretKeyMaterials as Record, - )) { - const { keyId, secretKeyMaterial } = remoteSecretKeyMaterial - crypto.subtle - .importKey('raw', secretKeyMaterial.buffer, 'HKDF', false, ['deriveBits', 'deriveKey']) - .then((material) => { - generateDeriveKey(material).then((deriveKey) => { - setRemoteDeriveKey(connectionId, keyId, deriveKey) - }) - - generateWriteIV(material).then((writeIV) => { - setWriteIV(connectionId, keyId, writeIV) - }) - - setLatestRemoteKeyId(connectionId, keyId) - }) - } - } else if (type === 'removeRemoteDeriveKey') { - const { connectionId } = event.data - removeDeriveKey(connectionId) - } else if (type === 'encrypt') { - const { readableStream, writableStream } = event.data - const transformStream = new TransformStream({ - transform: encryptFunction, - }) - readableStream.pipeThrough(transformStream).pipeTo(writableStream) - } else if (type === 'decrypt') { - const { readableStream, writableStream } = event.data - const transformStream = new TransformStream({ - transform: decryptFunction, - }) - readableStream.pipeThrough(transformStream).pipeTo(writableStream) - } else if (type === 'clear') { - countMap.clear() - writeIVMap.clear() - remoteDeriveKeyMap.clear() - latestRemoteKeyIdMap.clear() - selfDeriveKeyMap.clear() - } -} diff --git a/packages/e2ee/src/worker/sframe.ts b/packages/e2ee/src/worker/sframe.ts deleted file mode 100644 index 150c5955..00000000 --- a/packages/e2ee/src/worker/sframe.ts +++ /dev/null @@ -1,121 +0,0 @@ -const connectionIdLength = 26 - -function byteCount(n: number): number { - if (n === 0) { - return 1 - } - // log256(x) = log(x) / log(256) - return Math.floor(Math.log(n) / Math.log(2 ** 8) + 1) -} - -function arrayBufferToNumber(arrayBuffer: ArrayBuffer): number { - // 32bit までを想定 (BigInt への書き換え時に要修正) - const newArrayBuffer = new ArrayBuffer(Uint32Array.BYTES_PER_ELEMENT) - const newDataView = new DataView(newArrayBuffer) - - const dataView = new DataView(arrayBuffer) - - const paddingLength = Uint32Array.BYTES_PER_ELEMENT - dataView.byteLength - - for (let i = 0; i < paddingLength; i += 1) { - newDataView.setUint8(i, 0) - } - - for (let i = paddingLength, j = 0; i < Uint32Array.BYTES_PER_ELEMENT; i += 1, j += 1) { - newDataView.setUint8(i, dataView.getUint8(j)) - } - - return newDataView.getUint32(0) -} - -function encodeSFrameHeader(s: number, count: number, keyId: number): Uint8Array { - // 0 1 2 3 4 5 6 7 - // +-+-+-+-+-+-+-+-+---------------------------+---------------------------+ - // |S|LEN |1|KLEN | KID... (length=KLEN) | CTR... (length=LEN) | - // +-+-+-+-+-+-+-+-+---------------------------+---------------------------+ - // S: 1 bit - // LEN: 3 bit - // X: 1 bit - // KLEN: 3 bit - // KID: KLEN byte - // CTR: LEN byte - - // TODO: keyId (KID) が Number.MAX_SAFE_INTEGER, 7 byte を超えていた場合はエラーか例外 - // TODO: count (CTR) が Number.MAX_SAFE_INTEGER, 7 byte を超えていた場合はエラーか例外 - if (maxKeyId < keyId || maxCount < count) { - throw new Error('EXCEEDED-MAXIMUM-BROADCASTING-TIME') - } - - const klen = byteCount(keyId) - const len = byteCount(count) - - const headerBuffer = new ArrayBuffer(1 + klen + len) - const headerDataView = new DataView(headerBuffer) - // S, LEN, 1, KLEN で 1 byte - headerDataView.setUint8(0, (s << 7) + (len << 4) + (1 << 3) + klen) - - const headerUint8Array = new Uint8Array(headerBuffer) - - const keyIdBuffer = new ArrayBuffer(Uint32Array.BYTES_PER_ELEMENT) - const keyIdDataView = new DataView(keyIdBuffer) - keyIdDataView.setUint32(0, keyId) - const keyIdUint8Array = new Uint8Array(keyIdBuffer) - headerUint8Array.set(keyIdUint8Array.subarray(Uint32Array.BYTES_PER_ELEMENT - klen), 1) - - const countBuffer = new ArrayBuffer(Uint32Array.BYTES_PER_ELEMENT) - const countDataView = new DataView(countBuffer) - countDataView.setUint32(0, count) - const countUint8Array = new Uint8Array(countBuffer) - headerUint8Array.set(countUint8Array.subarray(Uint32Array.BYTES_PER_ELEMENT - len), klen + 1) - - return headerUint8Array -} - -function splitHeader(sframe: ArrayBuffer): [ArrayBuffer, ArrayBuffer, ArrayBuffer] { - const sframeDataView = new DataView(sframe) - const header = sframeDataView.getUint8(0) - const len = (header & 0x70) >> 4 - const klen = header & 0x07 - - const sframeHeaderLength = 1 + klen + len - const sframeHeader = sframe.slice(0, sframeHeaderLength) - - if (sframeHeader.byteLength < sframeHeaderLength) { - throw new Error('UNEXPECTED-SFRAME-LENGTH') - } - - const connectionId = sframe.slice(sframeHeaderLength, sframeHeaderLength + connectionIdLength) - - const encryptedFrame = sframe.slice(sframeHeaderLength + connectionIdLength, sframe.byteLength) - - return [sframeHeader, connectionId, encryptedFrame] -} - -function parseSFrameHeader(sframeHeader: ArrayBuffer): [number, number, number] { - const sframeHeaderDataView = new DataView(sframeHeader) - const header = sframeHeaderDataView.getUint8(0) - - const s = (header & 0x80) >> 7 - const len = (header & 0x70) >> 4 - const x = (header & 0x08) >> 3 - const klen = header & 0x07 - - // x flag - if (x !== 1) { - throw new Error('UNEXPECTED-X-FLAG') - } - - const headerLength = 1 + klen + len - - if (sframeHeaderDataView.byteLength < headerLength) { - throw new Error('UNEXPECTED-SFRAME-HEADER-LENGTH') - } - - const keyIdBuffer = sframeHeader.slice(1, 1 + klen) - const keyId = arrayBufferToNumber(keyIdBuffer) - - const countBuffer = sframeHeader.slice(1 + klen, headerLength) - const count = arrayBufferToNumber(countBuffer) - - return [s, count, keyId] -} diff --git a/packages/e2ee/tsconfig.json b/packages/e2ee/tsconfig.json deleted file mode 100644 index f941f0f2..00000000 --- a/packages/e2ee/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declarationDir": "." - }, - "include": ["src/sora_e2ee.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/e2ee/tsconfig.worker.json b/packages/e2ee/tsconfig.worker.json deleted file mode 100644 index 517d1ca3..00000000 --- a/packages/e2ee/tsconfig.worker.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "module": "amd", - "strict": true, - "strictNullChecks": true, - "importHelpers": true, - "moduleResolution": "node", - "experimentalDecorators": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "newLine": "LF", - "types": [], - "lib": [ - "esnext", - "es2017", - "webWorker" - ], - "outFile": "./_worker/sora_e2ee_worker.js" - }, - "include": [ - "src/worker/index.ts" - ], - "exclude": [ - "node_modules" - ] -} diff --git a/packages/go-wasm/package.json b/packages/go-wasm/package.json deleted file mode 100644 index 206bd2f6..00000000 --- a/packages/go-wasm/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@sora/go-wasm", - "version": "2021.1.0", - "main": "src/wasm_exec.js", - "module": "src/wasm_exec.js", - "types": "src/wasm_exec.d.ts", - "files": [ - "dist" - ], - "scripts": { - "build": "echo \"no build\"", - "lint": "echo \"no lint\"", - "fmt": "echo \"no fmt\"", - "test": "echo \"no test\"", - "check": "echo \"no check\"" - } -} \ No newline at end of file diff --git a/packages/go-wasm/src/wasm_exec.d.ts b/packages/go-wasm/src/wasm_exec.d.ts deleted file mode 100644 index a12c4e2f..00000000 --- a/packages/go-wasm/src/wasm_exec.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare function WasmExec(): void; -export default WasmExec; diff --git a/packages/go-wasm/src/wasm_exec.js b/packages/go-wasm/src/wasm_exec.js deleted file mode 100644 index 5288af96..00000000 --- a/packages/go-wasm/src/wasm_exec.js +++ /dev/null @@ -1,590 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -export default function () { - (() => { - // Map multiple JavaScript environments to a single common API, - // preferring web standards over Node.js API. - // - // Environments considered: - // - Browsers - // - Node.js - // - Electron - // - Parcel - - if (typeof global !== "undefined") { - // global already exists - } else if (typeof window !== "undefined") { - window.global = window; - } else if (typeof self !== "undefined") { - self.global = self; - } else { - throw new Error("cannot export Go (neither global, window nor self is defined)"); - } - - if (!global.require && typeof require !== "undefined") { - global.require = require; - } - - if (!global.fs && global.require) { - const fs = require("fs"); - if (Object.keys(fs) !== 0) { - global.fs = fs; - } - } - - const enosys = () => { - const err = new Error("not implemented"); - err.code = "ENOSYS"; - return err; - }; - - if (!global.fs) { - let outputBuf = ""; - global.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused - writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); - } - return buf.length; - }, - write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - callback(enosys()); - return; - } - const n = this.writeSync(fd, buf); - callback(null, n); - }, - chmod(path, mode, callback) { callback(enosys()); }, - chown(path, uid, gid, callback) { callback(enosys()); }, - close(fd, callback) { callback(enosys()); }, - fchmod(fd, mode, callback) { callback(enosys()); }, - fchown(fd, uid, gid, callback) { callback(enosys()); }, - fstat(fd, callback) { callback(enosys()); }, - fsync(fd, callback) { callback(null); }, - ftruncate(fd, length, callback) { callback(enosys()); }, - lchown(path, uid, gid, callback) { callback(enosys()); }, - link(path, link, callback) { callback(enosys()); }, - lstat(path, callback) { callback(enosys()); }, - mkdir(path, perm, callback) { callback(enosys()); }, - open(path, flags, mode, callback) { callback(enosys()); }, - read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, - readdir(path, callback) { callback(enosys()); }, - readlink(path, callback) { callback(enosys()); }, - rename(from, to, callback) { callback(enosys()); }, - rmdir(path, callback) { callback(enosys()); }, - stat(path, callback) { callback(enosys()); }, - symlink(path, link, callback) { callback(enosys()); }, - truncate(path, length, callback) { callback(enosys()); }, - unlink(path, callback) { callback(enosys()); }, - utimes(path, atime, mtime, callback) { callback(enosys()); }, - }; - } - - if (!global.process) { - global.process = { - getuid() { return -1; }, - getgid() { return -1; }, - geteuid() { return -1; }, - getegid() { return -1; }, - getgroups() { throw enosys(); }, - pid: -1, - ppid: -1, - umask() { throw enosys(); }, - cwd() { throw enosys(); }, - chdir() { throw enosys(); }, - } - } - - if (!global.crypto) { - const nodeCrypto = require("crypto"); - global.crypto = { - getRandomValues(b) { - nodeCrypto.randomFillSync(b); - }, - }; - } - - if (!global.performance) { - global.performance = { - now() { - const [sec, nsec] = process.hrtime(); - return sec * 1000 + nsec / 1000000; - }, - }; - } - - if (!global.TextEncoder) { - global.TextEncoder = require("util").TextEncoder; - } - - if (!global.TextDecoder) { - global.TextDecoder = require("util").TextDecoder; - } - - // End of polyfills for common API. - - const encoder = new TextEncoder("utf-8"); - const decoder = new TextDecoder("utf-8"); - - global.Go = class { - constructor() { - this.argv = ["js"]; - this.env = {}; - this.exit = (code) => { - if (code !== 0) { - console.warn("exit code:", code); - } - }; - this._exitPromise = new Promise((resolve) => { - this._resolveExitPromise = resolve; - }); - this._pendingEvent = null; - this._scheduledTimeouts = new Map(); - this._nextCallbackTimeoutID = 1; - - const setInt64 = (addr, v) => { - this.mem.setUint32(addr + 0, v, true); - this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); - } - - const getInt64 = (addr) => { - const low = this.mem.getUint32(addr + 0, true); - const high = this.mem.getInt32(addr + 4, true); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = this.mem.getFloat64(addr, true); - if (f === 0) { - return undefined; - } - if (!isNaN(f)) { - return f; - } - - const id = this.mem.getUint32(addr, true); - return this._values[id]; - } - - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; - - if (typeof v === "number" && v !== 0) { - if (isNaN(v)) { - this.mem.setUint32(addr + 4, nanHead, true); - this.mem.setUint32(addr, 0, true); - return; - } - this.mem.setFloat64(addr, v, true); - return; - } - - if (v === undefined) { - this.mem.setFloat64(addr, 0, true); - return; - } - - let id = this._ids.get(v); - if (id === undefined) { - id = this._idPool.pop(); - if (id === undefined) { - id = this._values.length; - } - this._values[id] = v; - this._goRefCounts[id] = 0; - this._ids.set(v, id); - } - this._goRefCounts[id]++; - let typeFlag = 0; - switch (typeof v) { - case "object": - if (v !== null) { - typeFlag = 1; - } - break; - case "string": - typeFlag = 2; - break; - case "symbol": - typeFlag = 3; - break; - case "function": - typeFlag = 4; - break; - } - this.mem.setUint32(addr + 4, nanHead | typeFlag, true); - this.mem.setUint32(addr, id, true); - } - - const loadSlice = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - return new Uint8Array(this._inst.exports.mem.buffer, array, len); - } - - const loadSliceOfValues = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - const a = new Array(len); - for (let i = 0; i < len; i++) { - a[i] = loadValue(array + i * 8); - } - return a; - } - - const loadString = (addr) => { - const saddr = getInt64(addr + 0); - const len = getInt64(addr + 8); - return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); - } - - const timeOrigin = Date.now() - performance.now(); - this.importObject = { - go: { - // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) - // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported - // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). - // This changes the SP, thus we have to update the SP used by the imported function. - - // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { - const code = this.mem.getInt32(sp + 8, true); - this.exited = true; - delete this._inst; - delete this._values; - delete this._goRefCounts; - delete this._ids; - delete this._idPool; - this.exit(code); - }, - - // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { - const fd = getInt64(sp + 8); - const p = getInt64(sp + 16); - const n = this.mem.getInt32(sp + 24, true); - fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); - }, - - // func resetMemoryDataView() - "runtime.resetMemoryDataView": (sp) => { - this.mem = new DataView(this._inst.exports.mem.buffer); - }, - - // func nanotime1() int64 - "runtime.nanotime1": (sp) => { - setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); - }, - - // func walltime1() (sec int64, nsec int32) - "runtime.walltime1": (sp) => { - const msec = (new Date).getTime(); - setInt64(sp + 8, msec / 1000); - this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); - }, - - // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { - const id = this._nextCallbackTimeoutID; - this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { - this._resume(); - while (this._scheduledTimeouts.has(id)) { - // for some reason Go failed to register the timeout event, log and try again - // (temporary workaround for https://github.com/golang/go/issues/28975) - console.warn("scheduleTimeoutEvent: missed timeout event"); - this._resume(); - } - }, - getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early - )); - this.mem.setInt32(sp + 16, id, true); - }, - - // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { - const id = this.mem.getInt32(sp + 8, true); - clearTimeout(this._scheduledTimeouts.get(id)); - this._scheduledTimeouts.delete(id); - }, - - // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { - crypto.getRandomValues(loadSlice(sp + 8)); - }, - - // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { - const id = this.mem.getUint32(sp + 8, true); - this._goRefCounts[id]--; - if (this._goRefCounts[id] === 0) { - const v = this._values[id]; - this._values[id] = null; - this._ids.delete(v); - this._idPool.push(id); - } - }, - - // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { - storeValue(sp + 24, loadString(sp + 8)); - }, - - // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { - const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 32, result); - }, - - // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (sp) => { - Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); - }, - - // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (sp) => { - Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); - }, - - // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (sp) => { - storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); - }, - - // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { - Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); - }, - - // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (sp) => { - try { - const v = loadValue(sp + 8); - const m = Reflect.get(v, loadString(sp + 16)); - const args = loadSliceOfValues(sp + 32); - const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 56, result); - this.mem.setUint8(sp + 64, 1); - } catch (err) { - storeValue(sp + 56, err); - this.mem.setUint8(sp + 64, 0); - } - }, - - // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 40, result); - this.mem.setUint8(sp + 48, 1); - } catch (err) { - storeValue(sp + 40, err); - this.mem.setUint8(sp + 48, 0); - } - }, - - // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { - setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); - }, - - // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { - const str = encoder.encode(String(loadValue(sp + 8))); - storeValue(sp + 16, str); - setInt64(sp + 24, str.length); - }, - - // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { - const str = loadValue(sp + 8); - loadSlice(sp + 16).set(str); - }, - - // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (sp) => { - this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); - }, - - // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (sp) => { - const dst = loadSlice(sp + 8); - const src = loadValue(sp + 32); - if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - // func copyBytesToJS(dst ref, src []byte) (int, bool) - "syscall/js.copyBytesToJS": (sp) => { - const dst = loadValue(sp + 8); - const src = loadSlice(sp + 16); - if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { - this.mem.setUint8(sp + 48, 0); - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - setInt64(sp + 40, toCopy.length); - this.mem.setUint8(sp + 48, 1); - }, - - "debug": (value) => { - console.log(value); - }, - } - }; - } - - async run(instance) { - this._inst = instance; - this.mem = new DataView(this._inst.exports.mem.buffer); - this._values = [ // JS values that Go currently has references to, indexed by reference id - NaN, - 0, - null, - true, - false, - global, - this, - ]; - this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map([ // mapping from JS values to reference ids - [0, 1], - [null, 2], - [true, 3], - [false, 4], - [global, 5], - [this, 6], - ]); - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited - - // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. - let offset = 4096; - - const strPtr = (str) => { - const ptr = offset; - const bytes = encoder.encode(str + "\0"); - new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); - offset += bytes.length; - if (offset % 8 !== 0) { - offset += 8 - (offset % 8); - } - return ptr; - }; - - const argc = this.argv.length; - - const argvPtrs = []; - this.argv.forEach((arg) => { - argvPtrs.push(strPtr(arg)); - }); - argvPtrs.push(0); - - const keys = Object.keys(this.env).sort(); - keys.forEach((key) => { - argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); - }); - argvPtrs.push(0); - - const argv = offset; - argvPtrs.forEach((ptr) => { - this.mem.setUint32(offset, ptr, true); - this.mem.setUint32(offset + 4, 0, true); - offset += 8; - }); - - this._inst.exports.run(argc, argv); - if (this.exited) { - this._resolveExitPromise(); - } - await this._exitPromise; - } - - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } - this._inst.exports.resume(); - if (this.exited) { - this._resolveExitPromise(); - } - } - - _makeFuncWrapper(id) { - const go = this; - return function () { - const event = { id: id, this: this, args: arguments }; - go._pendingEvent = event; - go._resume(); - return event.result; - }; - } - } - - if ( - global.require && - global.require.main === module && - global.process && - global.process.versions && - !global.process.versions.electron - ) { - if (process.argv.length < 3) { - console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); - process.exit(1); - } - - const go = new Go(); - go.argv = process.argv.slice(2); - go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); - go.exit = process.exit; - WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { - process.on("exit", (code) => { // Node.js exits if no event handler is pending - if (code === 0 && !go.exited) { - // deadlock, make Go print error and stack traces - go._pendingEvent = { id: 0 }; - go._resume(); - } - }); - return go.run(result.instance); - }).catch((err) => { - console.error(err); - process.exit(1); - }); - } - })(); -} diff --git a/packages/sdk/package.json b/packages/sdk/package.json index ecfe145e..519ed1aa 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -3,8 +3,8 @@ "scripts": { "build": "rollup -c rollup.config.mjs --bundleConfigAsCjs", "watch": "rollup -c -w", - "lint": "biome lint ./src", - "fmt": "biome format --write ./src", + "lint": "biome lint src", + "fmt": "biome format --write src", "check": "tsc --noEmit", "test": "vitest run" }, @@ -18,7 +18,6 @@ "rollup-plugin-delete": "2.0.0" }, "dependencies": { - "@sora/e2ee": "workspace:^", "fflate": "0.8.2" } } \ No newline at end of file diff --git a/packages/sdk/src/base.ts b/packages/sdk/src/base.ts index 237fc0c9..fd3c996b 100644 --- a/packages/sdk/src/base.ts +++ b/packages/sdk/src/base.ts @@ -1,6 +1,5 @@ import { zlibSync } from 'fflate' -import SoraE2EE from '@sora/e2ee' import type { Callbacks, ConnectionOptions, @@ -33,8 +32,6 @@ import { createSignalingMessage, createTimelineEvent, decompressMessage, - getPreKeyBundle, - getSignalingNotifyAuthnMetadata, getSignalingNotifyData, isFirefox, isSafari, @@ -195,16 +192,6 @@ export default class ConnectionBase { * イベントコールバックのリスト */ protected callbacks: Callbacks - /** - * E2EE インスタンス - * - * @internal - */ - protected e2ee: SoraE2EE | null - /** - * キーとなる sender が setupSenderTransform で初期化済みかどうか - */ - private senderStreamInitialized: WeakSet = new WeakSet() constructor( signalingUrlCandidates: string | string[], @@ -269,7 +256,6 @@ export default class ConnectionBase { datachannel: (): void => {}, } this.authMetadata = null - this.e2ee = null this.connectionTimeoutTimerId = 0 this.monitorSignalingWebSocketEventTimerId = 0 this.monitorIceConnectionStateChangeTimerId = 0 @@ -499,7 +485,6 @@ export default class ConnectionBase { } stream.addTrack(audioTrack) await transceiver.sender.replaceTrack(audioTrack) - await this.setupSenderTransform(transceiver.sender) } /** @@ -531,7 +516,6 @@ export default class ConnectionBase { } stream.addTrack(videoTrack) await transceiver.sender.replaceTrack(videoTrack) - await this.setupSenderTransform(transceiver.sender) } /** @@ -552,9 +536,6 @@ export default class ConnectionBase { if (this.pc) { this.pc.close() } - if (this.e2ee) { - this.e2ee.terminateWorker() - } this.initializeConnection() } @@ -613,10 +594,6 @@ export default class ConnectionBase { if (this.pc) { this.pc.close() } - // E2EE worker を終了する - if (this.e2ee) { - this.e2ee.terminateWorker() - } this.initializeConnection() const event = this.soraCloseEvent('abend', title) this.callbacks.disconnect(event) @@ -720,9 +697,6 @@ export default class ConnectionBase { } await this.disconnectWebSocket(title) await this.disconnectPeerConnection() - if (this.e2ee) { - this.e2ee.terminateWorker() - } this.initializeConnection() if (title === 'WEBSOCKET-ONCLOSE' && params && (params.code === 1000 || params.code === 1005)) { const event = this.soraCloseEvent('normal', 'DISCONNECT', params) @@ -753,7 +727,6 @@ export default class ConnectionBase { this.pc = null this.encodings = [] this.authMetadata = null - this.e2ee = null this.soraDataChannels = {} this.mids = { audio: '', @@ -1029,9 +1002,6 @@ export default class ConnectionBase { event = this.soraCloseEvent('normal', 'DISCONNECT', reason) } } - if (this.e2ee) { - this.e2ee.terminateWorker() - } this.initializeConnection() if (event) { if (event.type === 'abend') { @@ -1043,39 +1013,6 @@ export default class ConnectionBase { } } - /** - * E2EE の初期設定をするメソッド - */ - protected setupE2EE(): void { - if (this.options.e2ee === true) { - this.e2ee = new SoraE2EE() - this.e2ee.onWorkerDisconnect = async (): Promise => { - await this.abend('INTERNAL-ERROR', { reason: 'CRASH-E2EE-WORKER' }) - } - this.e2ee.startWorker() - } - } - - /** - * E2EE を開始するメソッド - */ - protected startE2EE(): void { - if (this.options.e2ee === true && this.e2ee) { - if (!this.connectionId) { - const error = new Error() - error.message = 'E2EE failed. Self connectionId is null' - throw error - } - this.e2ee.clearWorker() - const result = this.e2ee.start(this.connectionId) - this.e2ee.postSelfSecretKeyMaterial( - this.connectionId, - result.selfKeyId, - result.selfSecretKeyMaterial, - ) - } - } - /** * シグナリングに使う WebSocket インスタンスを作成するメソッド * @@ -1232,12 +1169,6 @@ export default class ConnectionBase { reject(error) } ws.onmessage = async (event): Promise => { - // E2EE 時専用処理 - if (event.data instanceof ArrayBuffer) { - this.writeWebSocketSignalingLog('onmessage-e2ee', event.data) - this.signalingOnMessageE2EE(event.data) - return - } if (typeof event.data !== 'string') { throw new Error('Received invalid signaling data') } @@ -1292,11 +1223,6 @@ export default class ConnectionBase { reject(error) return } - if (signalingMessage.e2ee && this.e2ee) { - const initResult = await this.e2ee.init() - // @ts-ignore signalingMessage の e2ee が true の場合は signalingNotifyMetadata が必ず object になる - signalingMessage.signaling_notify_metadata.pre_key_bundle = initResult - } this.trace('SIGNALING CONNECT MESSAGE', signalingMessage) if (ws) { ws.send(JSON.stringify(signalingMessage)) @@ -1319,10 +1245,6 @@ export default class ConnectionBase { */ protected async connectPeerConnection(message: SignalingOfferMessage): Promise { let config = Object.assign({}, message.config) - if (this.e2ee) { - // @ts-ignore https://w3c.github.io/webrtc-encoded-transform/#specification - config = Object.assign({ encodedInsertableStreams: true }, config) - } if (window.RTCPeerConnection.generateCertificate !== undefined) { const certificate = await window.RTCPeerConnection.generateCertificate({ name: 'ECDSA', @@ -1470,61 +1392,6 @@ export default class ConnectionBase { return sdp } - /** - * E2EE あるいはカスタムコーデックが有効になっている場合に、送信側の WebRTC Encoded Transform をセットアップする - * - * @param sender 対象となる RTCRtpSender インスタンス - */ - protected async setupSenderTransform(sender: RTCRtpSender): Promise { - if (this.e2ee === null || sender.track === null) { - return - } - // 既に初期化済み - if (this.senderStreamInitialized.has(sender)) { - return - } - - if ('transform' in RTCRtpSender.prototype) { - // WebRTC Encoded Transform に対応しているブラウザ - return - } - - // 古い API (i.e., createEncodedStreams) を使っているブラウザ - - // @ts-ignore - const senderStreams = sender.createEncodedStreams() as TransformStream - const readable = senderStreams.readable - this.e2ee.setupSenderTransform(readable, senderStreams.writable) - this.senderStreamInitialized.add(sender) - } - - /** - * E2EE あるいはカスタムコーデックが有効になっている場合に、受信側の WebRTC Encoded Transform をセットアップする - * - * @param mid コーデックの判別に使う mid - * @param receiver 対象となる RTCRtpReceiver インスタンス - */ - protected async setupReceiverTransform( - mid: string | null, - receiver: RTCRtpReceiver, - ): Promise { - if (this.e2ee === null) { - return - } - - if ('transform' in RTCRtpSender.prototype) { - // WebRTC Encoded Transform に対応しているブラウザ - return - } - - // 古い API (i.e., createEncodedStreams) を使っているブラウザ - - // @ts-ignore - const receiverStreams = receiver.createEncodedStreams() as TransformStream - const writable = receiverStreams.writable - this.e2ee.setupReceiverTransform(receiverStreams.readable, writable) - } - /** * シグナリングサーバーに type answer を投げるメソッド */ @@ -1877,22 +1744,6 @@ export default class ConnectionBase { return offer } - /** - * シグナリングサーバーから受け取った type e2ee メッセージを処理をするメソッド - * - * @param data - E2EE 用バイナリメッセージ - */ - private signalingOnMessageE2EE(data: ArrayBuffer): void { - if (this.e2ee) { - const message = new Uint8Array(data) - const result = this.e2ee.receiveMessage(message) - this.e2ee.postRemoteSecretKeyMaterials(result) - result.messages.filter((message) => { - this.sendE2EEMessage(message.buffer) - }) - } - } - /** * シグナリングサーバーから受け取った type offer メッセージを処理をするメソッド * @@ -2011,50 +1862,7 @@ export default class ConnectionBase { transportType: TransportType, ): void { if (message.event_type === 'connection.created') { - const connectionId = message.connection_id - if (this.connectionId !== connectionId) { - const authnMetadata = getSignalingNotifyAuthnMetadata(message) - const preKeyBundle = getPreKeyBundle(authnMetadata) - if (preKeyBundle && this.e2ee && connectionId) { - const result = this.e2ee.startSession(connectionId, preKeyBundle) - this.e2ee.postRemoteSecretKeyMaterials(result) - result.messages.filter((message) => { - this.sendE2EEMessage(message.buffer) - }) - // messages を送信し終えてから、selfSecretKeyMaterial を更新する - this.e2ee.postSelfSecretKeyMaterial( - result.selfConnectionId, - result.selfKeyId, - result.selfSecretKeyMaterial, - ) - } - } const data = getSignalingNotifyData(message) - data.filter((metadata) => { - const authnMetadata = getSignalingNotifyAuthnMetadata(metadata) - const preKeyBundle = getPreKeyBundle(authnMetadata) - const connectionId = metadata.connection_id - if (connectionId && this.e2ee && preKeyBundle) { - this.e2ee.addPreKeyBundle(connectionId, preKeyBundle) - } - }) - } else if (message.event_type === 'connection.destroyed') { - const authnMetadata = getSignalingNotifyAuthnMetadata(message) - const preKeyBundle = getPreKeyBundle(authnMetadata) - const connectionId = message.connection_id - if (preKeyBundle && this.e2ee && connectionId) { - const result = this.e2ee.stopSession(connectionId) - this.e2ee.postSelfSecretKeyMaterial( - result.selfConnectionId, - result.selfKeyId, - result.selfSecretKeyMaterial, - 5000, - ) - result.messages.filter((message) => { - this.sendE2EEMessage(message.buffer) - }) - this.e2ee.postRemoveRemoteDeriveKey(connectionId) - } } this.callbacks.notify(message, transportType) } @@ -2236,13 +2044,6 @@ export default class ConnectionBase { const message = JSON.parse(data) as SignalingPushMessage this.callbacks.push(message, 'datachannel') } - } else if (dataChannelEvent.channel.label === 'e2ee') { - dataChannelEvent.channel.onmessage = (event): void => { - const channel = event.currentTarget as RTCDataChannel - const data = event.data as ArrayBuffer - this.signalingOnMessageE2EE(data) - this.writeDataChannelSignalingLog('onmessage-e2ee', channel, data) - } } else if (dataChannelEvent.channel.label === 'stats') { dataChannelEvent.channel.onmessage = async (event): Promise => { const channel = event.currentTarget as RTCDataChannel @@ -2326,21 +2127,6 @@ export default class ConnectionBase { } } - /** - * シグナリングサーバーに E2E 用メッセージを投げるメソッド - * - * @param message - 送信するバイナリメッセージ - */ - private sendE2EEMessage(message: ArrayBuffer): void { - if (this.soraDataChannels.e2ee) { - this.soraDataChannels.e2ee.send(message) - this.writeDataChannelSignalingLog('send-e2ee', this.soraDataChannels.e2ee, message) - } else if (this.ws !== null) { - this.ws.send(message) - this.writeWebSocketSignalingLog('send-e2ee', message) - } - } - /** * シグナリングサーバーに stats メッセージを投げるメソッド * @@ -2455,26 +2241,6 @@ export default class ConnectionBase { } } - /** - * E2EE の自分のフィンガープリント - */ - get e2eeSelfFingerprint(): string | undefined { - if (this.options.e2ee && this.e2ee) { - return this.e2ee.selfFingerprint() - } - return undefined - } - - /** - * E2EE のリモートのフィンガープリントリスト - */ - get e2eeRemoteFingerprints(): Record | undefined { - if (this.options.e2ee && this.e2ee) { - return this.e2ee.remoteFingerprints() - } - return undefined - } - /** * audio が有効かどうか */ diff --git a/packages/sdk/src/publisher.ts b/packages/sdk/src/publisher.ts index 7333ef3d..95377033 100644 --- a/packages/sdk/src/publisher.ts +++ b/packages/sdk/src/publisher.ts @@ -47,14 +47,14 @@ export default class ConnectionPublisher extends ConnectionBase { /** * レガシーストリームで Sora へ接続するメソッド * + * @deprecated この関数は非推奨です、マルチストリームを利用してください。 + * * @param stream - メディアストリーム */ private async legacyStream(stream: MediaStream): Promise { await this.disconnect() - this.setupE2EE() const ws = await this.getSignalingWebSocket(this.signalingUrlCandidates) const signalingMessage = await this.signaling(ws) - this.startE2EE() await this.connectPeerConnection(signalingMessage) await this.setRemoteDescription(signalingMessage) stream.getTracks().filter((track) => { @@ -62,11 +62,6 @@ export default class ConnectionPublisher extends ConnectionBase { this.pc.addTrack(track, stream) } }) - if (this.pc) { - for (const sender of this.pc.getSenders()) { - await this.setupSenderTransform(sender) - } - } this.stream = stream await this.createAnswer(signalingMessage) this.sendAnswer() @@ -82,15 +77,11 @@ export default class ConnectionPublisher extends ConnectionBase { */ private async multiStream(stream: MediaStream): Promise { await this.disconnect() - this.setupE2EE() const ws = await this.getSignalingWebSocket(this.signalingUrlCandidates) const signalingMessage = await this.signaling(ws) - this.startE2EE() await this.connectPeerConnection(signalingMessage) if (this.pc) { this.pc.ontrack = async (event): Promise => { - await this.setupReceiverTransform(event.transceiver.mid, event.receiver) - const stream = event.streams[0] if (!stream) { return @@ -140,12 +131,6 @@ export default class ConnectionPublisher extends ConnectionBase { this.pc.addTrack(track, stream) } }) - if (this.pc) { - for (const sender of this.pc.getSenders()) { - await this.setupSenderTransform(sender) - } - } - this.stream = stream await this.createAnswer(signalingMessage) this.sendAnswer() diff --git a/packages/sdk/src/sora.ts b/packages/sdk/src/sora.ts index 79f61e0e..08e15b8c 100644 --- a/packages/sdk/src/sora.ts +++ b/packages/sdk/src/sora.ts @@ -1,5 +1,3 @@ -import SoraE2EE from '@sora/e2ee' - import type ConnectionBase from './base' import { applyMediaStreamConstraints } from './helpers' import ConnectionPublisher from './publisher' @@ -91,7 +89,7 @@ class SoraConnection { ) } /** - * role sendonly で接続するための Connecion インスタンスを生成するメソッド + * role sendonly で接続するための Connection インスタンスを生成するメソッド * * @param channelId - チャネルID * @param metadata - メタデータ @@ -123,7 +121,7 @@ class SoraConnection { ) } /** - * role recvonly で接続するための Connecion インスタンスを生成するメソッド + * role recvonly で接続するための Connection インスタンスを生成するメソッド * * @example * ```typescript @@ -169,20 +167,6 @@ class SoraConnection { * Sora JS SDK package */ export default { - /** - * E2EE で使用する WASM の読み込みを行うメソッド - * - * @example - * ```typescript - * Sora.initE2EE("http://192.0.2.100/wasm.wasm"); - * ``` - * @param wasmUrl - E2EE WASM の URL - * - * @public - */ - initE2EE: async (wasmUrl: string): Promise => { - await SoraE2EE.loadWasm(wasmUrl) - }, /** * SoraConnection インスタンスを生成するメソッド * diff --git a/packages/sdk/src/subscriber.ts b/packages/sdk/src/subscriber.ts index 68b21f44..9bb7bdea 100644 --- a/packages/sdk/src/subscriber.ts +++ b/packages/sdk/src/subscriber.ts @@ -45,18 +45,17 @@ export default class ConnectionSubscriber extends ConnectionBase { /** * レガシーストリームで Sora へ接続するメソッド + * + * @deprecated この関数は非推奨です、マルチストリームを利用してください。 + * */ private async legacyStream(): Promise { await this.disconnect() - this.setupE2EE() const ws = await this.getSignalingWebSocket(this.signalingUrlCandidates) const signalingMessage = await this.signaling(ws) - this.startE2EE() await this.connectPeerConnection(signalingMessage) if (this.pc) { this.pc.ontrack = async (event): Promise => { - await this.setupReceiverTransform(event.transceiver.mid, event.receiver) - this.stream = event.streams[0] const streamId = this.stream.id if (streamId === 'default') { @@ -109,15 +108,11 @@ export default class ConnectionSubscriber extends ConnectionBase { */ private async multiStream(): Promise { await this.disconnect() - this.setupE2EE() const ws = await this.getSignalingWebSocket(this.signalingUrlCandidates) const signalingMessage = await this.signaling(ws) - this.startE2EE() await this.connectPeerConnection(signalingMessage) if (this.pc) { this.pc.ontrack = async (event): Promise => { - await this.setupReceiverTransform(event.transceiver.mid, event.receiver) - const stream = event.streams[0] if (stream.id === 'default') { return diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index fbe8c264..d30f03e9 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -74,7 +74,6 @@ export type SignalingConnectMessage = { sdp: string sora_client: string environment: string - e2ee?: boolean spotlight_focus_rid?: SpotlightFocusRid spotlight_unfocus_rid?: SpotlightFocusRid data_channel_signaling?: boolean @@ -315,7 +314,6 @@ export type ConnectionOptions = { clientId?: string timeout?: number // deprecated option connectionTimeout?: number - e2ee?: boolean signalingNotifyMetadata?: JSONType dataChannelSignaling?: boolean ignoreDisconnectWebSocket?: boolean @@ -343,12 +341,6 @@ export type Callbacks = { datachannel: (event: DataChannelEvent) => void } -export type PreKeyBundle = { - identityKey: string - signedPreKey: string - preKeySignature: string -} - export type Browser = 'edge' | 'chrome' | 'safari' | 'opera' | 'firefox' | null export type TransportType = 'websocket' | 'datachannel' | 'peerconnection' diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts index 195f3fe6..362901af 100644 --- a/packages/sdk/src/utils.ts +++ b/packages/sdk/src/utils.ts @@ -7,7 +7,6 @@ import type { DataChannelEvent, DataChannelMessageEvent, JSONType, - PreKeyBundle, SignalingConnectDataChannel, SignalingConnectMessage, SignalingEvent, @@ -325,26 +324,6 @@ export function createSignalingMessage( if (message.simulcast && !enabledSimulcast() && role !== 'recvonly') { throw new Error('Simulcast can not be used with this browser') } - if (typeof options.e2ee === 'boolean') { - message.e2ee = options.e2ee - } - if (options.e2ee === true) { - if (message.signaling_notify_metadata === undefined) { - message.signaling_notify_metadata = {} - } - if ( - message.signaling_notify_metadata === null || - typeof message.signaling_notify_metadata !== 'object' - ) { - throw new Error("E2EE failed. Options signalingNotifyMetadata must be type 'object'") - } - if (message.video === true) { - message.video = {} - } - if (message.video) { - message.video.codec_type = 'VP8' - } - } if (Array.isArray(options.dataChannels) && 0 < options.dataChannels.length) { message.data_channels = parseDataChannelConfigurations(options.dataChannels) @@ -384,13 +363,6 @@ export function getSignalingNotifyData( return [] } -export function getPreKeyBundle(message: JSONType): PreKeyBundle | null { - if (typeof message === 'object' && message !== null && 'pre_key_bundle' in message) { - return message.pre_key_bundle as PreKeyBundle - } - return null -} - export function trace(clientId: string | null, title: string, value: unknown): void { const dump = (record: unknown) => { if (record && typeof record === 'object') { diff --git a/packages/sdk/tests/utils.test.ts b/packages/sdk/tests/utils.test.ts index 34352ef9..91255946 100644 --- a/packages/sdk/tests/utils.test.ts +++ b/packages/sdk/tests/utils.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest' -import { type AudioCodecType, DataChannelDirection, VideoCodecType } from '../src/types' +import type { AudioCodecType, DataChannelDirection, VideoCodecType } from '../src/types' import { createSignalingMessage } from '../src/utils' const channelId = '7N3fsMHob' @@ -395,70 +395,6 @@ test('createSignalingMessage video parameters', () => { ) }) -/** - * e2ee test - */ -test('createSignalingMessage e2ee: true', () => { - const options = { - e2ee: true, - e2eeWasmUrl: 'wasm', - } - const expectedMessage = Object.assign({}, baseExpectedMessage, { - e2ee: true, - video: { - codec_type: 'VP8', - }, - signaling_notify_metadata: {}, - }) - expect(createSignalingMessage(sdp, 'sendonly', channelId, undefined, options, false)).toEqual( - expectedMessage, - ) -}) - -test('createSignalingMessage e2ee: false', () => { - const options = { - e2ee: false, - } - const expectedMessage = Object.assign({}, baseExpectedMessage, { e2ee: false }) - expect(createSignalingMessage(sdp, 'sendonly', channelId, undefined, options, false)).toEqual( - expectedMessage, - ) -}) - -test('createSignalingMessage e2ee: true, video: false', () => { - const options = { - e2ee: true, - e2eeWasmUrl: 'wasm', - video: false, - } - const expectedMessage = Object.assign({}, baseExpectedMessage, { - e2ee: true, - video: false, - signaling_notify_metadata: {}, - }) - expect(createSignalingMessage(sdp, 'sendonly', channelId, undefined, options, false)).toEqual( - expectedMessage, - ) -}) - -test("createSignalingMessage e2ee: true, videoCodecType: 'VP9'", () => { - const options = { - e2ee: true, - e2eeWasmUrl: 'wasm', - videoCodecType: 'VP9' as VideoCodecType, - } - const expectedMessage = Object.assign({}, baseExpectedMessage, { - e2ee: true, - video: { - codec_type: 'VP8', - }, - signaling_notify_metadata: {}, - }) - expect(createSignalingMessage(sdp, 'sendonly', channelId, undefined, options, false)).toEqual( - expectedMessage, - ) -}) - /** * signalingNotifyMetadata test */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c4a3c85..6d66657b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: 5.5.4 version: 5.5.4 vite: - specifier: 5.4.2 - version: 5.4.2(@types/node@22.5.2) + specifier: 5.4.3 + version: 5.4.3(@types/node@22.5.3) vitest: specifier: 2.0.5 - version: 2.0.5(@types/node@22.5.2)(jsdom@25.0.0) + version: 2.0.5(@types/node@22.5.3)(jsdom@25.0.0) examples: dependencies: @@ -36,35 +36,8 @@ importers: specifier: workspace:* version: link:.. - packages/e2ee: - dependencies: - '@sora/go-wasm': - specifier: workspace:^ - version: link:../go-wasm - devDependencies: - '@rollup/plugin-node-resolve': - specifier: 15.2.3 - version: 15.2.3(rollup@4.21.2) - '@rollup/plugin-replace': - specifier: 5.0.7 - version: 5.0.7(rollup@4.21.2) - '@rollup/plugin-typescript': - specifier: 11.1.6 - version: 11.1.6(rollup@4.21.2)(tslib@2.7.0)(typescript@5.5.4) - rollup: - specifier: 4.21.2 - version: 4.21.2 - tslib: - specifier: 2.7.0 - version: 2.7.0 - - packages/go-wasm: {} - packages/sdk: dependencies: - '@sora/e2ee': - specifier: workspace:^ - version: link:../e2ee fflate: specifier: 0.8.2 version: 0.8.2 @@ -478,8 +451,8 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - '@types/node@22.5.2': - resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==} + '@types/node@22.5.3': + resolution: {integrity: sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -946,8 +919,8 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -963,8 +936,8 @@ packages: engines: {node: '>=18'} hasBin: true - postcss@8.4.43: - resolution: {integrity: sha512-gJAQVYbh5R3gYm33FijzCZj7CHyQ3hWMgJMprLUlIYqCwTeZhBQ19wp0e9mA25BUbEvY5+EXuuaAjqQsrBxQBQ==} + postcss@8.4.44: + resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==} engines: {node: ^10 || ^12 || >=14} psl@1.9.0: @@ -1144,8 +1117,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.2: - resolution: {integrity: sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==} + vite@5.4.3: + resolution: {integrity: sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -1260,8 +1233,8 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - yaml@2.5.0: - resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==} + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} hasBin: true @@ -1527,7 +1500,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 22.5.2 + '@types/node': 22.5.3 '@types/hast@3.0.4': dependencies: @@ -1535,7 +1508,7 @@ snapshots: '@types/minimatch@5.1.2': {} - '@types/node@22.5.2': + '@types/node@22.5.3': dependencies: undici-types: 6.19.8 @@ -2030,7 +2003,7 @@ snapshots: pathval@2.0.0: {} - picocolors@1.0.1: {} + picocolors@1.1.0: {} picomatch@2.3.1: {} @@ -2042,10 +2015,10 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - postcss@8.4.43: + postcss@8.4.44: dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 + picocolors: 1.1.0 source-map-js: 1.2.0 psl@1.9.0: {} @@ -2185,7 +2158,8 @@ snapshots: dependencies: punycode: 2.3.1 - tslib@2.7.0: {} + tslib@2.7.0: + optional: true typedoc@0.26.6(typescript@5.5.4): dependencies: @@ -2194,7 +2168,7 @@ snapshots: minimatch: 9.0.5 shiki: 1.16.1 typescript: 5.5.4 - yaml: 2.5.0 + yaml: 2.5.1 typescript@5.5.4: {} @@ -2209,13 +2183,13 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - vite-node@2.0.5(@types/node@22.5.2): + vite-node@2.0.5(@types/node@22.5.3): dependencies: cac: 6.7.14 debug: 4.3.6 pathe: 1.1.2 tinyrainbow: 1.2.0 - vite: 5.4.2(@types/node@22.5.2) + vite: 5.4.3(@types/node@22.5.3) transitivePeerDependencies: - '@types/node' - less @@ -2227,16 +2201,16 @@ snapshots: - supports-color - terser - vite@5.4.2(@types/node@22.5.2): + vite@5.4.3(@types/node@22.5.3): dependencies: esbuild: 0.21.5 - postcss: 8.4.43 + postcss: 8.4.44 rollup: 4.21.2 optionalDependencies: - '@types/node': 22.5.2 + '@types/node': 22.5.3 fsevents: 2.3.3 - vitest@2.0.5(@types/node@22.5.2)(jsdom@25.0.0): + vitest@2.0.5(@types/node@22.5.3)(jsdom@25.0.0): dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.5 @@ -2254,11 +2228,11 @@ snapshots: tinybench: 2.9.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.2(@types/node@22.5.2) - vite-node: 2.0.5(@types/node@22.5.2) + vite: 5.4.3(@types/node@22.5.3) + vite-node: 2.0.5(@types/node@22.5.3) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.5.2 + '@types/node': 22.5.3 jsdom: 25.0.0 transitivePeerDependencies: - less @@ -2316,4 +2290,4 @@ snapshots: xmlchars@2.2.0: {} - yaml@2.5.0: {} + yaml@2.5.1: {} diff --git a/tests/e2ee.spec.ts b/tests/e2ee.spec.ts deleted file mode 100644 index 45c5acdd..00000000 --- a/tests/e2ee.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { test } from '@playwright/test' - -// FIXME: ローカルでも通らない -test.skip('e2ee sendrecv x2', async ({ browser }) => { - // 新しいページを2つ作成 - const page1 = await browser.newPage() - const page2 = await browser.newPage() - - // それぞれのページに対して操作を行う - await page1.goto('http://localhost:9000/e2ee/') - await page2.goto('http://localhost:9000/e2ee/') - - await page1.click('#start') - await page2.click('#start') - - await page1.waitForSelector('#local-connection-id:not(:empty)') - const page1ConnectionId = await page1.$eval('#local-connection-id', (el) => el.textContent) - console.log(`e2ee-page1: connectionId=${page1ConnectionId}`) - await page1.waitForSelector('#local-fingerprint:not(:empty)') - const page1Fingerprint = await page1.$eval('#local-fingerprint', (el) => el.textContent) - console.log(`e2ee-page1: fingerprint=${page1Fingerprint}`) - - await page2.waitForSelector('#local-connection-id:not(:empty)') - const page2ConnectionId = await page2.$eval('#local-connection-id', (el) => el.textContent) - console.log(`e2ee-page2: connectionId=${page2ConnectionId}`) - await page2.waitForSelector('#local-fingerprint:not(:empty)') - const page2Fingerprint = await page2.$eval('#local-fingerprint', (el) => el.textContent) - console.log(`e2ee-page2: fingerprint=${page2Fingerprint}`) - - await page1.click('#stop') - await page2.click('#stop') - - await page1.close() - await page2.close() -}) diff --git a/vite.config.mts b/vite.config.mts index 7ea74c97..ac84d1f2 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -18,7 +18,6 @@ export default defineConfig({ spotlight_recvonly: resolve(__dirname, 'examples/spotlight_recvonly/index.html'), sendonly_audio_bit_rate: resolve(__dirname, 'examples/sendonly_audio_bit_rate/index.html'), messaging: resolve(__dirname, 'examples/messaging/index.html'), - e2ee: resolve(__dirname, 'examples/e2ee/index.html'), }, }, },