y-openrtc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +92 -0
- package/README.md +92 -0
- package/dist/broadcast.d.ts +25 -0
- package/dist/broadcast.js +65 -0
- package/dist/codec.d.ts +18 -0
- package/dist/codec.js +65 -0
- package/dist/crypto.d.ts +3 -0
- package/dist/crypto.js +56 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/provider.d.ts +82 -0
- package/dist/provider.js +762 -0
- package/dist/roomName.d.ts +1 -0
- package/dist/roomName.js +34 -0
- package/dist/runtimePool.d.ts +9 -0
- package/dist/runtimePool.js +93 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function roomNameToOpenrtcRoomId(roomName: string): Promise<string>;
|
package/dist/roomName.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const encoder = new TextEncoder();
|
|
2
|
+
function toHex(bytes) {
|
|
3
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
4
|
+
}
|
|
5
|
+
function fallbackHash(text) {
|
|
6
|
+
let hashA = 2166136261;
|
|
7
|
+
let hashB = 2166136261;
|
|
8
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
9
|
+
const code = text.charCodeAt(index);
|
|
10
|
+
hashA ^= code;
|
|
11
|
+
hashA = Math.imul(hashA, 16777619);
|
|
12
|
+
hashB ^= code + index;
|
|
13
|
+
hashB = Math.imul(hashB, 16777619);
|
|
14
|
+
}
|
|
15
|
+
const hexA = (hashA >>> 0).toString(16).padStart(8, '0');
|
|
16
|
+
const hexB = (hashB >>> 0).toString(16).padStart(8, '0');
|
|
17
|
+
return `${hexA}${hexB}`.repeat(3);
|
|
18
|
+
}
|
|
19
|
+
async function sha256Hex(text) {
|
|
20
|
+
const subtle = globalThis.crypto?.subtle;
|
|
21
|
+
if (!subtle) {
|
|
22
|
+
return fallbackHash(text);
|
|
23
|
+
}
|
|
24
|
+
const digest = await subtle.digest('SHA-256', encoder.encode(text));
|
|
25
|
+
return toHex(new Uint8Array(digest));
|
|
26
|
+
}
|
|
27
|
+
export async function roomNameToOpenrtcRoomId(roomName) {
|
|
28
|
+
const normalized = roomName.trim();
|
|
29
|
+
if (!normalized) {
|
|
30
|
+
throw new Error('roomName must be a non-empty string.');
|
|
31
|
+
}
|
|
32
|
+
const hex = await sha256Hex(normalized);
|
|
33
|
+
return `yjs_${hex.slice(0, 48)}`;
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RuntimeClient } from 'openrtc/runtime';
|
|
2
|
+
import type { OpenrtcProviderOptions } from './types.js';
|
|
3
|
+
export interface RuntimeLease {
|
|
4
|
+
runtime: RuntimeClient;
|
|
5
|
+
owned: boolean;
|
|
6
|
+
ensureReady(): Promise<RuntimeClient>;
|
|
7
|
+
release(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function acquireRuntimeLease(options: OpenrtcProviderOptions): RuntimeLease;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { OpenRTC } from 'openrtc';
|
|
2
|
+
const ownedRuntimes = new Map();
|
|
3
|
+
const runtimeStartPromises = new WeakMap();
|
|
4
|
+
function stableStringify(value) {
|
|
5
|
+
if (value === null || typeof value !== 'object') {
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(value)) {
|
|
9
|
+
return `[${value.map((item) => stableStringify(item)).join(',')}]`;
|
|
10
|
+
}
|
|
11
|
+
const entries = Object.entries(value)
|
|
12
|
+
.filter(([, current]) => current !== undefined)
|
|
13
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
14
|
+
return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${stableStringify(current)}`).join(',')}}`;
|
|
15
|
+
}
|
|
16
|
+
function runtimePoolKey(options) {
|
|
17
|
+
const space = options.spaceKey ?? options.space;
|
|
18
|
+
return stableStringify({
|
|
19
|
+
apiKey: options.apiKey,
|
|
20
|
+
authMode: options.authMode,
|
|
21
|
+
allowAnonymousHostedDefaults: options.allowAnonymousHostedDefaults,
|
|
22
|
+
space,
|
|
23
|
+
hasSpaceTokenProvider: typeof options.spaceTokenProvider === 'function',
|
|
24
|
+
storagePrefix: options.storagePrefix,
|
|
25
|
+
nodeIdPersistence: options.nodeIdPersistence,
|
|
26
|
+
transports: options.transports,
|
|
27
|
+
strictMode: options.strictMode,
|
|
28
|
+
hasTurnCredentialsProvider: typeof options.turnCredentialsProvider === 'function',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function ensureRuntimeReady(runtime) {
|
|
32
|
+
let startPromise = runtimeStartPromises.get(runtime);
|
|
33
|
+
if (!startPromise) {
|
|
34
|
+
startPromise = (async () => {
|
|
35
|
+
await runtime.initialize();
|
|
36
|
+
await runtime.start();
|
|
37
|
+
})();
|
|
38
|
+
runtimeStartPromises.set(runtime, startPromise);
|
|
39
|
+
}
|
|
40
|
+
await startPromise;
|
|
41
|
+
return runtime;
|
|
42
|
+
}
|
|
43
|
+
export function acquireRuntimeLease(options) {
|
|
44
|
+
if (options.runtime) {
|
|
45
|
+
const runtime = options.runtime;
|
|
46
|
+
return {
|
|
47
|
+
runtime,
|
|
48
|
+
owned: false,
|
|
49
|
+
ensureReady: () => ensureRuntimeReady(runtime),
|
|
50
|
+
release: async () => undefined,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const key = runtimePoolKey(options);
|
|
54
|
+
const space = options.spaceKey ?? options.space;
|
|
55
|
+
let entry = ownedRuntimes.get(key);
|
|
56
|
+
if (!entry) {
|
|
57
|
+
entry = {
|
|
58
|
+
runtime: OpenRTC({
|
|
59
|
+
apiKey: options.apiKey,
|
|
60
|
+
authMode: options.authMode,
|
|
61
|
+
allowAnonymousHostedDefaults: options.allowAnonymousHostedDefaults,
|
|
62
|
+
space,
|
|
63
|
+
spaceTokenProvider: options.spaceTokenProvider,
|
|
64
|
+
storagePrefix: options.storagePrefix,
|
|
65
|
+
nodeIdPersistence: options.nodeIdPersistence,
|
|
66
|
+
transports: options.transports,
|
|
67
|
+
strictMode: options.strictMode,
|
|
68
|
+
turnCredentialsProvider: options.turnCredentialsProvider,
|
|
69
|
+
}),
|
|
70
|
+
refs: 0,
|
|
71
|
+
};
|
|
72
|
+
ownedRuntimes.set(key, entry);
|
|
73
|
+
}
|
|
74
|
+
entry.refs += 1;
|
|
75
|
+
return {
|
|
76
|
+
runtime: entry.runtime,
|
|
77
|
+
owned: true,
|
|
78
|
+
ensureReady: () => ensureRuntimeReady(entry.runtime),
|
|
79
|
+
release: async () => {
|
|
80
|
+
const current = ownedRuntimes.get(key);
|
|
81
|
+
if (!current) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
current.refs -= 1;
|
|
85
|
+
if (current.refs > 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
ownedRuntimes.delete(key);
|
|
89
|
+
runtimeStartPromises.delete(current.runtime);
|
|
90
|
+
current.runtime.stop();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { OpenRTC } from 'openrtc';
|
|
2
|
+
import type { RuntimeClient } from 'openrtc/runtime';
|
|
3
|
+
import type { Awareness } from 'y-protocols/awareness';
|
|
4
|
+
type OpenrtcClientOptions = Parameters<typeof OpenRTC>[0];
|
|
5
|
+
export type OpenrtcTransports = OpenrtcClientOptions['transports'];
|
|
6
|
+
export type OpenrtcAuthMode = OpenrtcClientOptions['authMode'];
|
|
7
|
+
export type OpenrtcTurnCredentialsProvider = NonNullable<OpenrtcClientOptions>['turnCredentialsProvider'];
|
|
8
|
+
export type OpenrtcAllowAnonymousHostedDefaults = NonNullable<OpenrtcClientOptions>['allowAnonymousHostedDefaults'];
|
|
9
|
+
export type OpenrtcSpaceTokenProvider = NonNullable<OpenrtcClientOptions>['spaceTokenProvider'];
|
|
10
|
+
export interface OpenrtcProviderOptions {
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
runtime?: RuntimeClient;
|
|
13
|
+
authMode?: OpenrtcAuthMode;
|
|
14
|
+
allowAnonymousHostedDefaults?: OpenrtcAllowAnonymousHostedDefaults;
|
|
15
|
+
space?: string;
|
|
16
|
+
spaceKey?: string;
|
|
17
|
+
spaceTokenProvider?: OpenrtcSpaceTokenProvider;
|
|
18
|
+
awareness?: Awareness;
|
|
19
|
+
password?: string | null;
|
|
20
|
+
maxConns?: number;
|
|
21
|
+
filterBcConns?: boolean;
|
|
22
|
+
storagePrefix?: string;
|
|
23
|
+
nodeIdPersistence?: 'persistent' | 'ephemeral';
|
|
24
|
+
transports?: OpenrtcTransports;
|
|
25
|
+
strictMode?: boolean;
|
|
26
|
+
turnCredentialsProvider?: OpenrtcTurnCredentialsProvider;
|
|
27
|
+
roomIdOverride?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Ordered room ids to try when the preferred room is unavailable. This is
|
|
30
|
+
* useful for public demos where stale durable room members can make one
|
|
31
|
+
* fixed room appear full until backend cleanup catches up.
|
|
32
|
+
*/
|
|
33
|
+
roomIdCandidates?: string[];
|
|
34
|
+
signaling?: string[];
|
|
35
|
+
peerOpts?: Record<string, unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* When true, accept incoming P2P connections from any peer regardless of room
|
|
38
|
+
* membership. Use this with a custom `runtime` that skips room-based discovery
|
|
39
|
+
* (e.g. ticket-only / direct-connect mode). Off by default.
|
|
40
|
+
*/
|
|
41
|
+
acceptAllConnections?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface StatusEventPayload {
|
|
44
|
+
connected: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface SyncedEventPayload {
|
|
47
|
+
synced: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface PeersEventPayload {
|
|
50
|
+
added: string[];
|
|
51
|
+
removed: string[];
|
|
52
|
+
openrtcPeers: string[];
|
|
53
|
+
webrtcPeers: string[];
|
|
54
|
+
bcPeers: string[];
|
|
55
|
+
}
|
|
56
|
+
export interface OpenrtcProviderEvents {
|
|
57
|
+
status: StatusEventPayload;
|
|
58
|
+
synced: SyncedEventPayload;
|
|
59
|
+
peers: PeersEventPayload;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "y-openrtc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"description": "A Yjs provider that uses OpenRTC for room membership and peer transport.",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"yjs",
|
|
10
|
+
"crdt",
|
|
11
|
+
"realtime",
|
|
12
|
+
"openrtc",
|
|
13
|
+
"collaboration"
|
|
14
|
+
],
|
|
15
|
+
"author": "Bryant Hargreaves",
|
|
16
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
17
|
+
"homepage": "https://openrtc.app",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/bluestarburst/y-openrtc.git"
|
|
21
|
+
},
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"test": "vitest run tests",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"test:live": "OPENRTC_LIVE_TESTS=1 vitest run tests/OpenrtcProvider.live.test.ts"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"lib0": "^0.2.114",
|
|
44
|
+
"openrtc": "workspace:*",
|
|
45
|
+
"y-protocols": "^1.0.6"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"yjs": "^13.6.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^24.10.1",
|
|
52
|
+
"happy-dom": "^20.5.0",
|
|
53
|
+
"typescript": "^5.8.3",
|
|
54
|
+
"vitest": "^4.0.18",
|
|
55
|
+
"yjs": "^13.6.27"
|
|
56
|
+
}
|
|
57
|
+
}
|