@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 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;
@@ -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 eventBuffer = [];
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
- buffering = false;
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
- setImage: async (image, options$1) => {
150
- if (image === null) return manager.setImage(null, options$1);
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$1);
146
+ return manager.setImage(base64, options);
153
147
  }
154
148
  };
155
149
  if (isAvatarLive && audioStreamManager) {
156
- const manager$1 = audioStreamManager;
157
- client.playAudio = (audio) => manager$1.playAudio(audio);
150
+ const manager = audioStreamManager;
151
+ client.playAudio = (audio) => manager.playAudio(audio);
158
152
  }
159
- flushBufferedEvents();
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
- return { connect };
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.callbacks.isAvatarLive) this.pc.addTransceiver("video", { direction: "recvonly" });
244
- this.localStream.getTracks().forEach((track) => {
245
- if (this.pc && this.localStream) this.pc.addTrack(track, this.localStream);
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 || !this.localStream) return;
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
- const stream = this.localStream;
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, stream, CONNECTION_TIMEOUT, this.config.integration);
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.45",
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": {