connectbase-client 4.4.1 → 5.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/dist/index.mjs CHANGED
@@ -116,6 +116,123 @@ var AdsAPI = class {
116
116
  }
117
117
  };
118
118
 
119
+ // src/types/error.ts
120
+ var ApiError = class extends Error {
121
+ constructor(statusCode, message, code, details) {
122
+ super(message);
123
+ this.statusCode = statusCode;
124
+ this.name = "ApiError";
125
+ this.code = code;
126
+ this.details = details;
127
+ }
128
+ /**
129
+ * `JSON.stringify(err)` 가 `{}` 로 찍히지 않게 한다.
130
+ *
131
+ * 표준 `Error` 는 message/stack 이 non-enumerable 이라 JSON 직렬화 시 빈 객체가
132
+ * 된다. 로그를 `JSON.stringify` 로 남기는 소비자에게는 "에러 정보가 아무것도 안
133
+ * 온다"로 보여, 실제로는 있는 정보까지 없는 것으로 오진하게 만든다
134
+ * (platform-issue 019fa21c).
135
+ */
136
+ toJSON() {
137
+ return {
138
+ name: this.name,
139
+ message: this.message,
140
+ code: this.code,
141
+ statusCode: this.statusCode,
142
+ details: this.details
143
+ };
144
+ }
145
+ };
146
+ var AIError = class extends Error {
147
+ constructor(init) {
148
+ super(init.message || init.code || "AI request failed");
149
+ this.name = "AIError";
150
+ this.code = init.code || "stream_failed";
151
+ this.retryable = init.retryable ?? isRetryableAICode(this.code);
152
+ this.status = init.status;
153
+ this.provider = init.provider;
154
+ this.model = init.model;
155
+ this.retryAfter = init.retryAfter;
156
+ this.detailCode = init.detailCode;
157
+ this.sessionId = init.sessionId;
158
+ }
159
+ /** `JSON.stringify(err)` 가 `{}` 가 되지 않도록 한다 (ApiError.toJSON 참조). */
160
+ toJSON() {
161
+ return {
162
+ name: this.name,
163
+ code: this.code,
164
+ message: this.message,
165
+ retryable: this.retryable,
166
+ status: this.status,
167
+ provider: this.provider,
168
+ model: this.model,
169
+ retryAfter: this.retryAfter,
170
+ detailCode: this.detailCode,
171
+ sessionId: this.sessionId
172
+ };
173
+ }
174
+ };
175
+ function isRetryableAICode(code) {
176
+ return code === "provider_timeout" || code === "service_unavailable" || code === "rate_limit_exceeded" || code === "stream_failed";
177
+ }
178
+ function toAIError(payload, fallback) {
179
+ if (payload instanceof AIError) return payload;
180
+ const p = typeof payload === "object" && payload !== null ? payload : {};
181
+ const str = (v) => typeof v === "string" && v !== "" ? v : void 0;
182
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
183
+ const documented = str(p.error) ?? str(p.code) ?? fallback?.code;
184
+ const sub = str(p.detail_code) ?? str(p.detailCode) ?? str(p.code);
185
+ const code = documented ?? "stream_failed";
186
+ const detailCode = sub && sub !== code ? sub : void 0;
187
+ const message = str(p.message) ?? (payload instanceof Error ? payload.message : void 0) ?? fallback?.message ?? code;
188
+ return new AIError({
189
+ code,
190
+ message,
191
+ retryable: typeof p.retryable === "boolean" ? p.retryable : void 0,
192
+ status: num(p.status) ?? num(p.statusCode) ?? fallback?.status,
193
+ provider: str(p.provider),
194
+ model: str(p.model),
195
+ retryAfter: num(p.retry_after_seconds) ?? num(p.retryAfter),
196
+ detailCode,
197
+ sessionId: str(p.session_id) ?? str(p.sessionId) ?? fallback?.sessionId
198
+ });
199
+ }
200
+ var AuthError = class extends Error {
201
+ constructor(message) {
202
+ super(message);
203
+ this.name = "AuthError";
204
+ }
205
+ };
206
+ var GameError = class extends Error {
207
+ constructor(init) {
208
+ super(init.message || init.code || "GameError");
209
+ this.name = "GameError";
210
+ this.code = init.code || "UNKNOWN";
211
+ this.phase = init.phase;
212
+ this.feature = init.feature;
213
+ this.roomId = init.roomId;
214
+ this.scriptId = init.scriptId;
215
+ this.originClientId = init.originClientId;
216
+ this.requested = init.requested;
217
+ this.available = init.available;
218
+ }
219
+ /** `JSON.stringify(err)` 가 `{}` 가 되지 않도록 한다 (ApiError.toJSON 참조). */
220
+ toJSON() {
221
+ return {
222
+ name: this.name,
223
+ code: this.code,
224
+ message: this.message,
225
+ phase: this.phase,
226
+ feature: this.feature,
227
+ roomId: this.roomId,
228
+ scriptId: this.scriptId,
229
+ originClientId: this.originClientId,
230
+ requested: this.requested,
231
+ available: this.available
232
+ };
233
+ }
234
+ };
235
+
119
236
  // src/api/ai.ts
120
237
  var AIAPI = class {
121
238
  constructor(http) {
@@ -180,13 +297,24 @@ var AIAPI = class {
180
297
  signal
181
298
  });
182
299
  if (!response.ok) {
183
- const errorData = await response.json().catch(() => ({ error: "Stream request failed" }));
184
- callbacks.onError?.(errorData.error || "Stream request failed");
300
+ const errorData = await response.json().catch(() => ({ error: "stream_failed" }));
301
+ callbacks.onError?.(
302
+ toAIError(errorData, {
303
+ message: "Stream request failed",
304
+ status: response.status
305
+ })
306
+ );
185
307
  return;
186
308
  }
187
309
  reader = response.body?.getReader();
188
310
  if (!reader) {
189
- callbacks.onError?.("ReadableStream not supported");
311
+ callbacks.onError?.(
312
+ new AIError({
313
+ code: "stream_failed",
314
+ message: "ReadableStream not supported",
315
+ retryable: false
316
+ })
317
+ );
190
318
  return;
191
319
  }
192
320
  const decoder = new TextDecoder();
@@ -207,9 +335,7 @@ var AIAPI = class {
207
335
  try {
208
336
  const event = JSON.parse(data);
209
337
  if (event.error) {
210
- callbacks.onError?.(
211
- event.message || event.error || "stream error"
212
- );
338
+ callbacks.onError?.(toAIError(event));
213
339
  return;
214
340
  }
215
341
  if (event.type === "sources" && event.sources) {
@@ -1194,34 +1320,48 @@ var AnalyticsAPI = class {
1194
1320
  }
1195
1321
  };
1196
1322
 
1197
- // src/types/error.ts
1198
- var ApiError = class extends Error {
1199
- constructor(statusCode, message, code, details) {
1200
- super(message);
1201
- this.statusCode = statusCode;
1202
- this.name = "ApiError";
1203
- this.code = code;
1204
- this.details = details;
1323
+ // src/api/app-members.ts
1324
+ var AppMembersAPI = class {
1325
+ constructor(http) {
1326
+ this.http = http;
1205
1327
  }
1206
- };
1207
- var AuthError = class extends Error {
1208
- constructor(message) {
1209
- super(message);
1210
- this.name = "AuthError";
1328
+ ensureServerAuth(method) {
1329
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
1330
+ throw new Error(
1331
+ `cb.appMembers.${method}() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["app_member:read"]) \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Public Key(cb_pk_) \uB2E8\uB3C5 SDK \uC778\uC2A4\uD134\uC2A4\uB85C\uB294 \uD638\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC790\uAE30 \uC815\uBCF4\uB294 cb.auth.getMe() \uB97C \uC4F0\uC138\uC694.`
1332
+ );
1333
+ }
1211
1334
  }
1212
- };
1213
- var GameError = class extends Error {
1214
- constructor(init) {
1215
- super(init.message || init.code || "GameError");
1216
- this.name = "GameError";
1217
- this.code = init.code || "UNKNOWN";
1218
- this.phase = init.phase;
1219
- this.feature = init.feature;
1220
- this.roomId = init.roomId;
1221
- this.scriptId = init.scriptId;
1222
- this.originClientId = init.originClientId;
1223
- this.requested = init.requested;
1224
- this.available = init.available;
1335
+ /**
1336
+ * 멤버 목록을 조회한다 (이메일 포함). (management_scope: `app_member:read`)
1337
+ *
1338
+ * @param appId ID
1339
+ * @param options 페이지네이션 및 `search`(닉네임·이메일 부분 일치)
1340
+ */
1341
+ async list(appId, options = {}) {
1342
+ this.ensureServerAuth("list");
1343
+ const query = new URLSearchParams();
1344
+ if (options.page !== void 0) query.set("page", String(options.page));
1345
+ if (options.pageSize !== void 0)
1346
+ query.set("page_size", String(options.pageSize));
1347
+ const search = options.search?.trim();
1348
+ if (search) query.set("search", search);
1349
+ const qs = query.toString();
1350
+ return this.http.get(
1351
+ `/v1/apps/${appId}/app-members${qs ? `?${qs}` : ""}`
1352
+ );
1353
+ }
1354
+ /**
1355
+ * 앱 멤버 상세를 조회한다 (이메일 + 로그인 수단 포함). (management_scope: `app_member:read`)
1356
+ *
1357
+ * @param appId 앱 ID
1358
+ * @param memberId 멤버 ID
1359
+ */
1360
+ async get(appId, memberId) {
1361
+ this.ensureServerAuth("get");
1362
+ return this.http.get(
1363
+ `/v1/apps/${appId}/app-members/${memberId}`
1364
+ );
1225
1365
  }
1226
1366
  };
1227
1367
 
@@ -6748,9 +6888,16 @@ var RealtimeAPI = class {
6748
6888
  });
6749
6889
  this.pendingRequests.clear();
6750
6890
  this.subscriptions.clear();
6751
- this.streamSessions.forEach((session) => {
6891
+ this.streamSessions.forEach((session, sessionId) => {
6752
6892
  if (session.handlers.onError) {
6753
- session.handlers.onError(new Error("Connection closed"));
6893
+ session.handlers.onError(
6894
+ new AIError({
6895
+ code: "service_unavailable",
6896
+ message: "\uC2E4\uC2DC\uAC04 \uC5F0\uACB0\uC774 \uC885\uB8CC\uB418\uC5B4 AI \uC2A4\uD2B8\uB9AC\uBC0D\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC7AC\uC5F0\uACB0 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
6897
+ retryable: true,
6898
+ sessionId
6899
+ })
6900
+ );
6754
6901
  }
6755
6902
  });
6756
6903
  this.streamSessions.clear();
@@ -7015,17 +7162,26 @@ var RealtimeAPI = class {
7015
7162
  signal
7016
7163
  });
7017
7164
  if (!response.ok) {
7018
- const errData = await response.json().catch(() => ({ error: "Stream request failed" }));
7165
+ const errData = await response.json().catch(() => ({ error: "stream_failed" }));
7019
7166
  handlers.onError?.(
7020
- new Error(
7021
- errData.error || errData.message || "Stream request failed"
7022
- )
7167
+ toAIError(errData, {
7168
+ message: "Stream request failed",
7169
+ sessionId,
7170
+ status: response.status
7171
+ })
7023
7172
  );
7024
7173
  return;
7025
7174
  }
7026
7175
  reader = response.body?.getReader();
7027
7176
  if (!reader) {
7028
- handlers.onError?.(new Error("ReadableStream not supported"));
7177
+ handlers.onError?.(
7178
+ new AIError({
7179
+ code: "stream_failed",
7180
+ message: "ReadableStream not supported",
7181
+ retryable: false,
7182
+ sessionId
7183
+ })
7184
+ );
7029
7185
  return;
7030
7186
  }
7031
7187
  const decoder = new TextDecoder();
@@ -7052,9 +7208,7 @@ var RealtimeAPI = class {
7052
7208
  try {
7053
7209
  const ev = JSON.parse(data);
7054
7210
  if (ev.error) {
7055
- handlers.onError?.(
7056
- new Error(ev.message || ev.error || "stream error")
7057
- );
7211
+ handlers.onError?.(toAIError(ev, { sessionId }));
7058
7212
  return;
7059
7213
  }
7060
7214
  if (ev.type === "tool_start") {
@@ -7098,7 +7252,14 @@ var RealtimeAPI = class {
7098
7252
  } catch (err) {
7099
7253
  const aborted = signal.aborted || err instanceof DOMException && err.name === "AbortError" || typeof err === "object" && err !== null && err.name === "AbortError";
7100
7254
  if (!aborted) {
7101
- handlers.onError?.(err instanceof Error ? err : new Error(String(err)));
7255
+ handlers.onError?.(
7256
+ new AIError({
7257
+ code: "service_unavailable",
7258
+ message: err instanceof Error ? err.message : String(err) || "AI \uC2A4\uD2B8\uB9AC\uBC0D \uC694\uCCAD\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
7259
+ retryable: true,
7260
+ sessionId
7261
+ })
7262
+ );
7102
7263
  }
7103
7264
  } finally {
7104
7265
  this.sseSessions.delete(sessionId);
@@ -7736,12 +7897,11 @@ var RealtimeAPI = class {
7736
7897
  break;
7737
7898
  }
7738
7899
  case "stream_error": {
7739
- const data = msg.data;
7740
7900
  if (msg.request_id) {
7741
7901
  for (const [sessionId, session] of this.streamSessions) {
7742
7902
  if (session.requestId === msg.request_id) {
7743
7903
  if (session.handlers.onError) {
7744
- session.handlers.onError(new Error(data.message));
7904
+ session.handlers.onError(toAIError(msg.data, { sessionId }));
7745
7905
  }
7746
7906
  this.streamSessions.delete(sessionId);
7747
7907
  break;
@@ -7814,9 +7974,16 @@ var RealtimeAPI = class {
7814
7974
  }
7815
7975
  this.ws = null;
7816
7976
  this._connectionId = null;
7817
- this.streamSessions.forEach((session) => {
7977
+ this.streamSessions.forEach((session, sessionId) => {
7818
7978
  if (session.handlers.onError) {
7819
- session.handlers.onError(new Error("Connection lost"));
7979
+ session.handlers.onError(
7980
+ new AIError({
7981
+ code: "service_unavailable",
7982
+ message: "\uC2E4\uC2DC\uAC04 \uC5F0\uACB0\uC774 \uB04A\uACA8 AI \uC2A4\uD2B8\uB9AC\uBC0D\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC7AC\uC5F0\uACB0 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
7983
+ retryable: true,
7984
+ sessionId
7985
+ })
7986
+ );
7820
7987
  }
7821
7988
  });
7822
7989
  this.streamSessions.clear();
@@ -11273,13 +11440,26 @@ var HttpClient = class {
11273
11440
  this.emitError(err2);
11274
11441
  throw err2;
11275
11442
  }
11276
- const message = typeof rawError === "string" ? rawError : errorData.message || "Unknown error";
11277
- const legacyDetails = retryAfterSeconds !== void 0 ? { retry_after_seconds: retryAfterSeconds } : void 0;
11443
+ const flatMessage = typeof errorData.message === "string" && errorData.message !== "" ? errorData.message : void 0;
11444
+ const explicitCode = typeof errorData.code === "string" && errorData.code !== "" ? errorData.code : void 0;
11445
+ const errorIsCode = typeof rawError === "string" && /^[a-z][a-z0-9_]*$/.test(rawError);
11446
+ const message = flatMessage ?? (typeof rawError === "string" && rawError !== "" ? rawError : "Unknown error");
11447
+ const code = explicitCode ?? (errorIsCode ? rawError : void 0);
11448
+ const legacyDetails = {};
11449
+ if (retryAfterSeconds !== void 0) {
11450
+ legacyDetails.retry_after_seconds = retryAfterSeconds;
11451
+ }
11452
+ if (typeof errorData.provider === "string") {
11453
+ legacyDetails.provider = errorData.provider;
11454
+ }
11455
+ if (typeof errorData.model === "string") {
11456
+ legacyDetails.model = errorData.model;
11457
+ }
11278
11458
  const err = new ApiError(
11279
11459
  response.status,
11280
11460
  message,
11281
- void 0,
11282
- legacyDetails
11461
+ code,
11462
+ Object.keys(legacyDetails).length > 0 ? legacyDetails : void 0
11283
11463
  );
11284
11464
  this.emitError(err);
11285
11465
  throw err;
@@ -12163,6 +12343,7 @@ var ConnectBase = class {
12163
12343
  this.subscription = new SubscriptionAPI(this.http);
12164
12344
  this.push = new PushAPI(this.http);
12165
12345
  this.roles = new RolesAPI(this.http);
12346
+ this.appMembers = new AppMembersAPI(this.http);
12166
12347
  this.video = new VideoAPI(
12167
12348
  this.http,
12168
12349
  config.videoUrl || env("CB_VIDEO_URL") || DEFAULT_VIDEO_URL
@@ -12257,9 +12438,11 @@ var ConnectBase = class {
12257
12438
  var index_default = ConnectBase;
12258
12439
  export {
12259
12440
  AIAPI,
12441
+ AIError,
12260
12442
  AUTH_MEMBER_ID_TOKEN,
12261
12443
  AdsAPI,
12262
12444
  ApiError,
12445
+ AppMembersAPI,
12263
12446
  AuthError,
12264
12447
  ConnectBase,
12265
12448
  EndpointAPI,
@@ -12276,5 +12459,6 @@ export {
12276
12459
  detectInAppBrowser,
12277
12460
  escapeToExternalBrowser,
12278
12461
  isWebTransportSupported,
12462
+ toAIError,
12279
12463
  toCreateRoomWire
12280
12464
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "4.4.1",
3
+ "version": "5.1.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",