@decartai/sdk 0.0.44 → 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
@@ -4,8 +4,9 @@ import { ProcessClient } from "./process/client.js";
4
4
  import { JobStatus, JobStatusResponse, JobSubmitResponse, QueueJobResult, QueueSubmitAndPollOptions, QueueSubmitOptions } from "./queue/types.js";
5
5
  import { QueueClient } from "./queue/client.js";
6
6
  import { DecartSDKError, ERROR_CODES } from "./utils/errors.js";
7
- import { ConnectionState } from "./realtime/webrtc-connection.js";
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 };
@@ -1,4 +1,5 @@
1
1
  import { DecartSDKError } from "../utils/errors.js";
2
+ import { ConnectionState } from "./types.js";
2
3
  import { SetInput } from "./methods.js";
3
4
  import { z } from "zod";
4
5
 
@@ -40,7 +41,7 @@ declare const realTimeClientConnectOptionsSchema: z.ZodObject<{
40
41
  }, z.core.$strip>;
41
42
  type RealTimeClientConnectOptions = z.infer<typeof realTimeClientConnectOptionsSchema>;
42
43
  type Events = {
43
- connectionChange: "connected" | "connecting" | "disconnected" | "reconnecting";
44
+ connectionChange: ConnectionState;
44
45
  error: DecartSDKError;
45
46
  };
46
47
  type RealTimeClient = {
@@ -51,11 +52,12 @@ type RealTimeClient = {
51
52
  enhance?: boolean;
52
53
  }) => Promise<void>;
53
54
  isConnected: () => boolean;
54
- getConnectionState: () => "connected" | "connecting" | "disconnected" | "reconnecting";
55
+ getConnectionState: () => ConnectionState;
55
56
  disconnect: () => void;
56
57
  on: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
57
58
  off: <K extends keyof Events>(event: K, listener: (data: Events[K]) => void) => void;
58
- sessionId: string;
59
+ sessionId: string | null;
60
+ subscribeToken: string | null;
59
61
  setImage: (image: Blob | File | string | null, options?: {
60
62
  prompt?: string;
61
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;
@@ -90,16 +88,18 @@ const createRealTimeClient = (opts) => {
90
88
  text: initialState.prompt.text,
91
89
  enhance: initialState.prompt.enhance
92
90
  } : void 0;
91
+ const url = `${baseUrl}${options.model.urlPath}`;
92
+ const { emitter: eventEmitter, emitOrBuffer, flush, stop } = createEventBuffer();
93
93
  webrtcManager = new WebRTCManager({
94
- webrtcUrl: `${`${baseUrl}${options.model.urlPath}`}?api_key=${encodeURIComponent(apiKey)}&model=${encodeURIComponent(options.model.name)}`,
94
+ webrtcUrl: `${url}?api_key=${encodeURIComponent(apiKey)}&model=${encodeURIComponent(options.model.name)}`,
95
95
  integration,
96
96
  onRemoteStream,
97
97
  onConnectionStateChange: (state) => {
98
- eventEmitter.emit("connectionChange", state);
98
+ emitOrBuffer("connectionChange", state);
99
99
  },
100
100
  onError: (error) => {
101
101
  console.error("WebRTC error:", error);
102
- eventEmitter.emit("error", createWebrtcError(error));
102
+ emitOrBuffer("error", createWebrtcError(error));
103
103
  },
104
104
  customizeOffer: options.customizeOffer,
105
105
  vp8MinBitrate: 300,
@@ -109,6 +109,13 @@ const createRealTimeClient = (opts) => {
109
109
  initialPrompt
110
110
  });
111
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);
112
119
  await manager.connect(inputStream);
113
120
  const methods = realtimeMethods(manager, imageToBase64);
114
121
  if (!isAvatarLive && initialState?.prompt) {
@@ -121,22 +128,29 @@ const createRealTimeClient = (opts) => {
121
128
  isConnected: () => manager.isConnected(),
122
129
  getConnectionState: () => manager.getConnectionState(),
123
130
  disconnect: () => {
131
+ stop();
124
132
  manager.cleanup();
125
133
  audioStreamManager?.cleanup();
126
134
  },
127
135
  on: eventEmitter.on,
128
136
  off: eventEmitter.off,
129
- sessionId,
130
- setImage: async (image, options$1) => {
131
- 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);
132
145
  const base64 = await imageToBase64(image);
133
- return manager.setImage(base64, options$1);
146
+ return manager.setImage(base64, options);
134
147
  }
135
148
  };
136
149
  if (isAvatarLive && audioStreamManager) {
137
- const manager$1 = audioStreamManager;
138
- client.playAudio = (audio) => manager$1.playAudio(audio);
150
+ const manager = audioStreamManager;
151
+ client.playAudio = (audio) => manager.playAudio(audio);
139
152
  }
153
+ flush();
140
154
  return client;
141
155
  } catch (error) {
142
156
  webrtcManager?.cleanup();
@@ -144,7 +158,47 @@ const createRealTimeClient = (opts) => {
144
158
  throw error;
145
159
  }
146
160
  };
147
- 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
+ };
148
202
  };
149
203
 
150
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 };
@@ -20,7 +20,7 @@ const setPromptInputSchema = z.object({
20
20
  const realtimeMethods = (webrtcManager, imageToBase64) => {
21
21
  const assertConnected = () => {
22
22
  const state = webrtcManager.getConnectionState();
23
- if (state !== "connected") throw new Error(`Cannot send message: connection is ${state}`);
23
+ if (state !== "connected" && state !== "generating") throw new Error(`Cannot send message: connection is ${state}`);
24
24
  };
25
25
  const set = async (input) => {
26
26
  assertConnected();
@@ -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 };
@@ -0,0 +1,5 @@
1
+ //#region src/realtime/types.d.ts
2
+
3
+ type ConnectionState = "connecting" | "connected" | "generating" | "disconnected" | "reconnecting";
4
+ //#endregion
5
+ export { ConnectionState };
@@ -58,7 +58,7 @@ var WebRTCConnection = class {
58
58
  await this.setupNewPeerConnection();
59
59
  await Promise.race([new Promise((resolve, reject) => {
60
60
  const checkConnection = setInterval(() => {
61
- if (this.state === "connected") {
61
+ if (this.state === "connected" || this.state === "generating") {
62
62
  clearInterval(checkConnection);
63
63
  resolve();
64
64
  } else if (this.state === "disconnected") {
@@ -94,6 +94,14 @@ var WebRTCConnection = class {
94
94
  this.websocketMessagesEmitter.emit("promptAck", msg);
95
95
  return;
96
96
  }
97
+ if (msg.type === "generation_started") {
98
+ this.setState("generating");
99
+ return;
100
+ }
101
+ if (msg.type === "session_id") {
102
+ this.websocketMessagesEmitter.emit("sessionId", msg);
103
+ return;
104
+ }
97
105
  if (!this.pc) return;
98
106
  switch (msg.type) {
99
107
  case "ready": {
@@ -221,7 +229,6 @@ var WebRTCConnection = class {
221
229
  }
222
230
  }
223
231
  async setupNewPeerConnection(turnConfig) {
224
- if (!this.localStream) throw new Error("No local stream found");
225
232
  if (this.pc) {
226
233
  this.pc.getSenders().forEach((sender) => {
227
234
  if (sender.track && this.pc) this.pc.removeTrack(sender);
@@ -236,12 +243,23 @@ var WebRTCConnection = class {
236
243
  });
237
244
  this.pc = new RTCPeerConnection({ iceServers });
238
245
  this.setState("connecting");
239
- if (this.callbacks.isAvatarLive) this.pc.addTransceiver("video", { direction: "recvonly" });
240
- this.localStream.getTracks().forEach((track) => {
241
- if (this.pc && this.localStream) this.pc.addTrack(track, this.localStream);
242
- });
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;
243
256
  this.pc.ontrack = (e) => {
244
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
+ }
245
263
  };
246
264
  this.pc.onicecandidate = (e) => {
247
265
  this.send({
@@ -252,7 +270,9 @@ var WebRTCConnection = class {
252
270
  this.pc.onconnectionstatechange = () => {
253
271
  if (!this.pc) return;
254
272
  const s = this.pc.connectionState;
255
- this.setState(s === "connected" ? "connected" : ["connecting", "new"].includes(s) ? "connecting" : "disconnected");
273
+ const nextState = s === "connected" ? "connected" : ["connecting", "new"].includes(s) ? "connecting" : "disconnected";
274
+ if (this.state === "generating" && nextState !== "disconnected") return;
275
+ this.setState(nextState);
256
276
  };
257
277
  this.pc.oniceconnectionstatechange = () => {
258
278
  if (this.pc?.iceConnectionState === "failed") {
@@ -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;
@@ -43,7 +44,7 @@ var WebRTCManager = class {
43
44
  emitState(state) {
44
45
  if (this.managerState !== state) {
45
46
  this.managerState = state;
46
- if (state === "connected") this.hasConnected = true;
47
+ if (state === "connected" || state === "generating") this.hasConnected = true;
47
48
  this.config.onConnectionStateChange?.(state);
48
49
  }
49
50
  }
@@ -53,9 +54,9 @@ var WebRTCManager = class {
53
54
  return;
54
55
  }
55
56
  if (this.isReconnecting) {
56
- if (state === "connected") {
57
+ if (state === "connected" || state === "generating") {
57
58
  this.isReconnecting = false;
58
- this.emitState("connected");
59
+ this.emitState(state);
59
60
  }
60
61
  return;
61
62
  }
@@ -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;
@@ -137,7 +139,7 @@ var WebRTCManager = class {
137
139
  this.emitState("disconnected");
138
140
  }
139
141
  isConnected() {
140
- return this.managerState === "connected";
142
+ return this.managerState === "connected" || this.managerState === "generating";
141
143
  }
142
144
  getConnectionState() {
143
145
  return this.managerState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decartai/sdk",
3
- "version": "0.0.44",
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": {
@@ -1,7 +0,0 @@
1
- import "mitt";
2
-
3
- //#region src/realtime/webrtc-connection.d.ts
4
-
5
- type ConnectionState = "connecting" | "connected" | "disconnected" | "reconnecting";
6
- //#endregion
7
- export { ConnectionState };