@decartai/sdk 0.0.45 → 0.0.46
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/dist/index.d.ts +3 -1
- package/dist/realtime/client.d.ts +2 -1
- package/dist/realtime/client.js +64 -30
- package/dist/realtime/event-buffer.js +35 -0
- package/dist/realtime/subscribe-client.d.ts +22 -0
- package/dist/realtime/subscribe-client.js +20 -0
- package/dist/realtime/webrtc-connection.js +19 -5
- package/dist/realtime/webrtc-manager.js +6 -4
- package/package.json +1 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { QueueClient } from "./queue/client.js";
|
|
|
6
6
|
import { DecartSDKError, ERROR_CODES } from "./utils/errors.js";
|
|
7
7
|
import { ConnectionState } from "./realtime/types.js";
|
|
8
8
|
import { SetInput } from "./realtime/methods.js";
|
|
9
|
+
import { RealTimeSubscribeClient, SubscribeEvents, SubscribeOptions } from "./realtime/subscribe-client.js";
|
|
9
10
|
import { AvatarOptions, Events, RealTimeClient, RealTimeClientConnectOptions, RealTimeClientInitialState } from "./realtime/client.js";
|
|
10
11
|
import { ModelState } from "./shared/types.js";
|
|
11
12
|
import { CreateTokenResponse, TokensClient } from "./tokens/client.js";
|
|
@@ -46,6 +47,7 @@ type DecartClientOptions = {
|
|
|
46
47
|
declare const createDecartClient: (options?: DecartClientOptions) => {
|
|
47
48
|
realtime: {
|
|
48
49
|
connect: (stream: MediaStream | null, options: RealTimeClientConnectOptions) => Promise<RealTimeClient>;
|
|
50
|
+
subscribe: (options: SubscribeOptions) => Promise<RealTimeSubscribeClient>;
|
|
49
51
|
};
|
|
50
52
|
/**
|
|
51
53
|
* Client for synchronous image generation.
|
|
@@ -119,4 +121,4 @@ declare const createDecartClient: (options?: DecartClientOptions) => {
|
|
|
119
121
|
tokens: TokensClient;
|
|
120
122
|
};
|
|
121
123
|
//#endregion
|
|
122
|
-
export { type AvatarOptions, type ConnectionState, type CreateTokenResponse, DecartClientOptions, type DecartSDKError, ERROR_CODES, type FileInput, type ImageModelDefinition, type ImageModels, type JobStatus, type JobStatusResponse, type JobSubmitResponse, type Model, type ModelDefinition, type ModelState, type ProcessClient, type ProcessOptions, type QueueClient, type QueueJobResult, type QueueSubmitAndPollOptions, type QueueSubmitOptions, type ReactNativeFile, type RealTimeClient, type RealTimeClientConnectOptions, type RealTimeClientInitialState, type Events as RealTimeEvents, type RealTimeModels, type SetInput, type TokensClient, type VideoModelDefinition, type VideoModels, createDecartClient, isImageModel, isRealtimeModel, isVideoModel, models };
|
|
124
|
+
export { type AvatarOptions, type ConnectionState, type CreateTokenResponse, DecartClientOptions, type DecartSDKError, ERROR_CODES, type FileInput, type ImageModelDefinition, type ImageModels, type JobStatus, type JobStatusResponse, type JobSubmitResponse, type Model, type ModelDefinition, type ModelState, type ProcessClient, type ProcessOptions, type QueueClient, type QueueJobResult, type QueueSubmitAndPollOptions, type QueueSubmitOptions, type ReactNativeFile, type RealTimeClient, type RealTimeClientConnectOptions, type RealTimeClientInitialState, type Events as RealTimeEvents, type RealTimeModels, type RealTimeSubscribeClient, type SetInput, type SubscribeEvents, type SubscribeOptions, type TokensClient, type VideoModelDefinition, type VideoModels, createDecartClient, isImageModel, isRealtimeModel, isVideoModel, models };
|
|
@@ -56,7 +56,8 @@ type RealTimeClient = {
|
|
|
56
56
|
disconnect: () => void;
|
|
57
57
|
on: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
|
|
58
58
|
off: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
|
|
59
|
-
sessionId: string;
|
|
59
|
+
sessionId: string | null;
|
|
60
|
+
subscribeToken: string | null;
|
|
60
61
|
setImage: (image: Blob | File | string | null, options?: {
|
|
61
62
|
prompt?: string;
|
|
62
63
|
enhance?: boolean;
|
package/dist/realtime/client.js
CHANGED
|
@@ -2,11 +2,11 @@ import { createWebrtcError } from "../utils/errors.js";
|
|
|
2
2
|
import { modelDefinitionSchema } from "../shared/model.js";
|
|
3
3
|
import { modelStateSchema } from "../shared/types.js";
|
|
4
4
|
import { AudioStreamManager } from "./audio-stream-manager.js";
|
|
5
|
+
import { createEventBuffer } from "./event-buffer.js";
|
|
5
6
|
import { realtimeMethods } from "./methods.js";
|
|
7
|
+
import { decodeSubscribeToken, encodeSubscribeToken } from "./subscribe-client.js";
|
|
6
8
|
import { WebRTCManager } from "./webrtc-manager.js";
|
|
7
9
|
import { z } from "zod";
|
|
8
|
-
import mitt from "mitt";
|
|
9
|
-
import { v4 } from "uuid";
|
|
10
10
|
|
|
11
11
|
//#region src/realtime/client.ts
|
|
12
12
|
async function blobToBase64(blob) {
|
|
@@ -66,10 +66,8 @@ const realTimeClientConnectOptionsSchema = z.object({
|
|
|
66
66
|
const createRealTimeClient = (opts) => {
|
|
67
67
|
const { baseUrl, apiKey, integration } = opts;
|
|
68
68
|
const connect = async (stream, options) => {
|
|
69
|
-
const eventEmitter = mitt();
|
|
70
69
|
const parsedOptions = realTimeClientConnectOptionsSchema.safeParse(options);
|
|
71
70
|
if (!parsedOptions.success) throw parsedOptions.error;
|
|
72
|
-
const sessionId = v4();
|
|
73
71
|
const isAvatarLive = options.model.name === "live_avatar";
|
|
74
72
|
const { onRemoteStream, initialState, avatar } = parsedOptions.data;
|
|
75
73
|
let audioStreamManager;
|
|
@@ -91,22 +89,7 @@ const createRealTimeClient = (opts) => {
|
|
|
91
89
|
enhance: initialState.prompt.enhance
|
|
92
90
|
} : void 0;
|
|
93
91
|
const url = `${baseUrl}${options.model.urlPath}`;
|
|
94
|
-
const
|
|
95
|
-
let buffering = true;
|
|
96
|
-
const emitOrBuffer = (event, data) => {
|
|
97
|
-
if (buffering) eventBuffer.push({
|
|
98
|
-
event,
|
|
99
|
-
data
|
|
100
|
-
});
|
|
101
|
-
else eventEmitter.emit(event, data);
|
|
102
|
-
};
|
|
103
|
-
const flushBufferedEvents = () => {
|
|
104
|
-
setTimeout(() => {
|
|
105
|
-
buffering = false;
|
|
106
|
-
for (const { event, data } of eventBuffer) eventEmitter.emit(event, data);
|
|
107
|
-
eventBuffer.length = 0;
|
|
108
|
-
}, 0);
|
|
109
|
-
};
|
|
92
|
+
const { emitter: eventEmitter, emitOrBuffer, flush, stop } = createEventBuffer();
|
|
110
93
|
webrtcManager = new WebRTCManager({
|
|
111
94
|
webrtcUrl: `${url}?api_key=${encodeURIComponent(apiKey)}&model=${encodeURIComponent(options.model.name)}`,
|
|
112
95
|
integration,
|
|
@@ -126,6 +109,13 @@ const createRealTimeClient = (opts) => {
|
|
|
126
109
|
initialPrompt
|
|
127
110
|
});
|
|
128
111
|
const manager = webrtcManager;
|
|
112
|
+
let sessionId = null;
|
|
113
|
+
let subscribeToken = null;
|
|
114
|
+
const sessionIdListener = (msg) => {
|
|
115
|
+
subscribeToken = encodeSubscribeToken(msg.session_id, msg.server_ip, msg.server_port);
|
|
116
|
+
sessionId = msg.session_id;
|
|
117
|
+
};
|
|
118
|
+
manager.getWebsocketMessageEmitter().on("sessionId", sessionIdListener);
|
|
129
119
|
await manager.connect(inputStream);
|
|
130
120
|
const methods = realtimeMethods(manager, imageToBase64);
|
|
131
121
|
if (!isAvatarLive && initialState?.prompt) {
|
|
@@ -138,25 +128,29 @@ const createRealTimeClient = (opts) => {
|
|
|
138
128
|
isConnected: () => manager.isConnected(),
|
|
139
129
|
getConnectionState: () => manager.getConnectionState(),
|
|
140
130
|
disconnect: () => {
|
|
141
|
-
|
|
142
|
-
eventBuffer.length = 0;
|
|
131
|
+
stop();
|
|
143
132
|
manager.cleanup();
|
|
144
133
|
audioStreamManager?.cleanup();
|
|
145
134
|
},
|
|
146
135
|
on: eventEmitter.on,
|
|
147
136
|
off: eventEmitter.off,
|
|
148
|
-
sessionId
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
get sessionId() {
|
|
138
|
+
return sessionId;
|
|
139
|
+
},
|
|
140
|
+
get subscribeToken() {
|
|
141
|
+
return subscribeToken;
|
|
142
|
+
},
|
|
143
|
+
setImage: async (image, options) => {
|
|
144
|
+
if (image === null) return manager.setImage(null, options);
|
|
151
145
|
const base64 = await imageToBase64(image);
|
|
152
|
-
return manager.setImage(base64, options
|
|
146
|
+
return manager.setImage(base64, options);
|
|
153
147
|
}
|
|
154
148
|
};
|
|
155
149
|
if (isAvatarLive && audioStreamManager) {
|
|
156
|
-
const manager
|
|
157
|
-
client.playAudio = (audio) => manager
|
|
150
|
+
const manager = audioStreamManager;
|
|
151
|
+
client.playAudio = (audio) => manager.playAudio(audio);
|
|
158
152
|
}
|
|
159
|
-
|
|
153
|
+
flush();
|
|
160
154
|
return client;
|
|
161
155
|
} catch (error) {
|
|
162
156
|
webrtcManager?.cleanup();
|
|
@@ -164,7 +158,47 @@ const createRealTimeClient = (opts) => {
|
|
|
164
158
|
throw error;
|
|
165
159
|
}
|
|
166
160
|
};
|
|
167
|
-
|
|
161
|
+
const subscribe = async (options) => {
|
|
162
|
+
const { sid, ip, port } = decodeSubscribeToken(options.token);
|
|
163
|
+
const subscribeUrl = `${baseUrl}/subscribe/${encodeURIComponent(sid)}?IP=${encodeURIComponent(ip)}&port=${encodeURIComponent(port)}&api_key=${encodeURIComponent(apiKey)}`;
|
|
164
|
+
const { emitter: eventEmitter, emitOrBuffer, flush, stop } = createEventBuffer();
|
|
165
|
+
let webrtcManager;
|
|
166
|
+
try {
|
|
167
|
+
webrtcManager = new WebRTCManager({
|
|
168
|
+
webrtcUrl: subscribeUrl,
|
|
169
|
+
integration,
|
|
170
|
+
onRemoteStream: options.onRemoteStream,
|
|
171
|
+
onConnectionStateChange: (state) => {
|
|
172
|
+
emitOrBuffer("connectionChange", state);
|
|
173
|
+
},
|
|
174
|
+
onError: (error) => {
|
|
175
|
+
console.error("WebRTC subscribe error:", error);
|
|
176
|
+
emitOrBuffer("error", createWebrtcError(error));
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const manager = webrtcManager;
|
|
180
|
+
await manager.connect(null);
|
|
181
|
+
const client = {
|
|
182
|
+
isConnected: () => manager.isConnected(),
|
|
183
|
+
getConnectionState: () => manager.getConnectionState(),
|
|
184
|
+
disconnect: () => {
|
|
185
|
+
stop();
|
|
186
|
+
manager.cleanup();
|
|
187
|
+
},
|
|
188
|
+
on: eventEmitter.on,
|
|
189
|
+
off: eventEmitter.off
|
|
190
|
+
};
|
|
191
|
+
flush();
|
|
192
|
+
return client;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
webrtcManager?.cleanup();
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
connect,
|
|
200
|
+
subscribe
|
|
201
|
+
};
|
|
168
202
|
};
|
|
169
203
|
|
|
170
204
|
//#endregion
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import mitt from "mitt";
|
|
2
|
+
|
|
3
|
+
//#region src/realtime/event-buffer.ts
|
|
4
|
+
function createEventBuffer() {
|
|
5
|
+
const emitter = mitt();
|
|
6
|
+
const buffer = [];
|
|
7
|
+
let buffering = true;
|
|
8
|
+
const emitOrBuffer = (event, data) => {
|
|
9
|
+
if (buffering) buffer.push({
|
|
10
|
+
event,
|
|
11
|
+
data
|
|
12
|
+
});
|
|
13
|
+
else emitter.emit(event, data);
|
|
14
|
+
};
|
|
15
|
+
const flush = () => {
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
buffering = false;
|
|
18
|
+
for (const { event, data } of buffer) emitter.emit(event, data);
|
|
19
|
+
buffer.length = 0;
|
|
20
|
+
}, 0);
|
|
21
|
+
};
|
|
22
|
+
const stop = () => {
|
|
23
|
+
buffering = false;
|
|
24
|
+
buffer.length = 0;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
emitter,
|
|
28
|
+
emitOrBuffer,
|
|
29
|
+
flush,
|
|
30
|
+
stop
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { createEventBuffer };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DecartSDKError } from "../utils/errors.js";
|
|
2
|
+
import { ConnectionState } from "./types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/realtime/subscribe-client.d.ts
|
|
5
|
+
|
|
6
|
+
type SubscribeEvents = {
|
|
7
|
+
connectionChange: ConnectionState;
|
|
8
|
+
error: DecartSDKError;
|
|
9
|
+
};
|
|
10
|
+
type RealTimeSubscribeClient = {
|
|
11
|
+
isConnected: () => boolean;
|
|
12
|
+
getConnectionState: () => ConnectionState;
|
|
13
|
+
disconnect: () => void;
|
|
14
|
+
on: <K extends keyof SubscribeEvents>(event: K, listener: (data: SubscribeEvents[K]) => void) => void;
|
|
15
|
+
off: <K extends keyof SubscribeEvents>(event: K, listener: (data: SubscribeEvents[K]) => void) => void;
|
|
16
|
+
};
|
|
17
|
+
type SubscribeOptions = {
|
|
18
|
+
token: string;
|
|
19
|
+
onRemoteStream: (stream: MediaStream) => void;
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { RealTimeSubscribeClient, SubscribeEvents, SubscribeOptions };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/realtime/subscribe-client.ts
|
|
2
|
+
function encodeSubscribeToken(sessionId, serverIp, serverPort) {
|
|
3
|
+
return btoa(JSON.stringify({
|
|
4
|
+
sid: sessionId,
|
|
5
|
+
ip: serverIp,
|
|
6
|
+
port: serverPort
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
function decodeSubscribeToken(token) {
|
|
10
|
+
try {
|
|
11
|
+
const payload = JSON.parse(atob(token));
|
|
12
|
+
if (!payload.sid || !payload.ip || !payload.port) throw new Error("Invalid subscribe token format");
|
|
13
|
+
return payload;
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("Invalid subscribe token");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { decodeSubscribeToken, encodeSubscribeToken };
|
|
@@ -98,6 +98,10 @@ var WebRTCConnection = class {
|
|
|
98
98
|
this.setState("generating");
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
|
+
if (msg.type === "session_id") {
|
|
102
|
+
this.websocketMessagesEmitter.emit("sessionId", msg);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
101
105
|
if (!this.pc) return;
|
|
102
106
|
switch (msg.type) {
|
|
103
107
|
case "ready": {
|
|
@@ -225,7 +229,6 @@ var WebRTCConnection = class {
|
|
|
225
229
|
}
|
|
226
230
|
}
|
|
227
231
|
async setupNewPeerConnection(turnConfig) {
|
|
228
|
-
if (!this.localStream) throw new Error("No local stream found");
|
|
229
232
|
if (this.pc) {
|
|
230
233
|
this.pc.getSenders().forEach((sender) => {
|
|
231
234
|
if (sender.track && this.pc) this.pc.removeTrack(sender);
|
|
@@ -240,12 +243,23 @@ var WebRTCConnection = class {
|
|
|
240
243
|
});
|
|
241
244
|
this.pc = new RTCPeerConnection({ iceServers });
|
|
242
245
|
this.setState("connecting");
|
|
243
|
-
if (this.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
246
|
+
if (this.localStream) {
|
|
247
|
+
if (this.callbacks.isAvatarLive) this.pc.addTransceiver("video", { direction: "recvonly" });
|
|
248
|
+
this.localStream.getTracks().forEach((track) => {
|
|
249
|
+
if (this.pc && this.localStream) this.pc.addTrack(track, this.localStream);
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
this.pc.addTransceiver("video", { direction: "recvonly" });
|
|
253
|
+
this.pc.addTransceiver("audio", { direction: "recvonly" });
|
|
254
|
+
}
|
|
255
|
+
let fallbackStream = null;
|
|
247
256
|
this.pc.ontrack = (e) => {
|
|
248
257
|
if (e.streams?.[0]) this.callbacks.onRemoteStream?.(e.streams[0]);
|
|
258
|
+
else {
|
|
259
|
+
if (!fallbackStream) fallbackStream = new MediaStream();
|
|
260
|
+
fallbackStream.addTrack(e.track);
|
|
261
|
+
this.callbacks.onRemoteStream?.(fallbackStream);
|
|
262
|
+
}
|
|
249
263
|
};
|
|
250
264
|
this.pc.onicecandidate = (e) => {
|
|
251
265
|
this.send({
|
|
@@ -21,6 +21,7 @@ var WebRTCManager = class {
|
|
|
21
21
|
connection;
|
|
22
22
|
config;
|
|
23
23
|
localStream = null;
|
|
24
|
+
subscribeMode = false;
|
|
24
25
|
managerState = "disconnected";
|
|
25
26
|
hasConnected = false;
|
|
26
27
|
isReconnecting = false;
|
|
@@ -66,17 +67,17 @@ var WebRTCManager = class {
|
|
|
66
67
|
this.emitState(state);
|
|
67
68
|
}
|
|
68
69
|
async reconnect() {
|
|
69
|
-
if (this.isReconnecting || this.intentionalDisconnect
|
|
70
|
+
if (this.isReconnecting || this.intentionalDisconnect) return;
|
|
71
|
+
if (!this.subscribeMode && !this.localStream) return;
|
|
70
72
|
const reconnectGeneration = ++this.reconnectGeneration;
|
|
71
73
|
this.isReconnecting = true;
|
|
72
74
|
this.emitState("reconnecting");
|
|
73
75
|
try {
|
|
74
76
|
await pRetry(async () => {
|
|
75
77
|
if (this.intentionalDisconnect || reconnectGeneration !== this.reconnectGeneration) throw new AbortError("Reconnect cancelled");
|
|
76
|
-
|
|
77
|
-
if (!stream) throw new AbortError("Reconnect cancelled: no local stream");
|
|
78
|
+
if (!this.subscribeMode && !this.localStream) throw new AbortError("Reconnect cancelled: no local stream");
|
|
78
79
|
this.connection.cleanup();
|
|
79
|
-
await this.connection.connect(this.config.webrtcUrl,
|
|
80
|
+
await this.connection.connect(this.config.webrtcUrl, this.localStream, CONNECTION_TIMEOUT, this.config.integration);
|
|
80
81
|
if (this.intentionalDisconnect || reconnectGeneration !== this.reconnectGeneration) {
|
|
81
82
|
this.connection.cleanup();
|
|
82
83
|
throw new AbortError("Reconnect cancelled");
|
|
@@ -103,6 +104,7 @@ var WebRTCManager = class {
|
|
|
103
104
|
}
|
|
104
105
|
async connect(localStream) {
|
|
105
106
|
this.localStream = localStream;
|
|
107
|
+
this.subscribeMode = localStream === null;
|
|
106
108
|
this.intentionalDisconnect = false;
|
|
107
109
|
this.hasConnected = false;
|
|
108
110
|
this.isReconnecting = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decartai/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.46",
|
|
4
4
|
"description": "Decart's JavaScript SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"mitt": "^3.0.1",
|
|
45
45
|
"p-retry": "^6.2.1",
|
|
46
|
-
"uuid": "^13.0.0",
|
|
47
46
|
"zod": "^4.0.17"
|
|
48
47
|
},
|
|
49
48
|
"scripts": {
|