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.js CHANGED
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AIAPI: () => AIAPI,
24
+ AIError: () => AIError,
24
25
  AUTH_MEMBER_ID_TOKEN: () => AUTH_MEMBER_ID_TOKEN,
25
26
  AdsAPI: () => AdsAPI,
26
27
  ApiError: () => ApiError,
28
+ AppMembersAPI: () => AppMembersAPI,
27
29
  AuthError: () => AuthError,
28
30
  ConnectBase: () => ConnectBase,
29
31
  EndpointAPI: () => EndpointAPI,
@@ -40,6 +42,7 @@ __export(index_exports, {
40
42
  detectInAppBrowser: () => detectInAppBrowser,
41
43
  escapeToExternalBrowser: () => escapeToExternalBrowser,
42
44
  isWebTransportSupported: () => isWebTransportSupported,
45
+ toAIError: () => toAIError,
43
46
  toCreateRoomWire: () => toCreateRoomWire
44
47
  });
45
48
  module.exports = __toCommonJS(index_exports);
@@ -162,6 +165,123 @@ var AdsAPI = class {
162
165
  }
163
166
  };
164
167
 
168
+ // src/types/error.ts
169
+ var ApiError = class extends Error {
170
+ constructor(statusCode, message, code, details) {
171
+ super(message);
172
+ this.statusCode = statusCode;
173
+ this.name = "ApiError";
174
+ this.code = code;
175
+ this.details = details;
176
+ }
177
+ /**
178
+ * `JSON.stringify(err)` 가 `{}` 로 찍히지 않게 한다.
179
+ *
180
+ * 표준 `Error` 는 message/stack 이 non-enumerable 이라 JSON 직렬화 시 빈 객체가
181
+ * 된다. 로그를 `JSON.stringify` 로 남기는 소비자에게는 "에러 정보가 아무것도 안
182
+ * 온다"로 보여, 실제로는 있는 정보까지 없는 것으로 오진하게 만든다
183
+ * (platform-issue 019fa21c).
184
+ */
185
+ toJSON() {
186
+ return {
187
+ name: this.name,
188
+ message: this.message,
189
+ code: this.code,
190
+ statusCode: this.statusCode,
191
+ details: this.details
192
+ };
193
+ }
194
+ };
195
+ var AIError = class extends Error {
196
+ constructor(init) {
197
+ super(init.message || init.code || "AI request failed");
198
+ this.name = "AIError";
199
+ this.code = init.code || "stream_failed";
200
+ this.retryable = init.retryable ?? isRetryableAICode(this.code);
201
+ this.status = init.status;
202
+ this.provider = init.provider;
203
+ this.model = init.model;
204
+ this.retryAfter = init.retryAfter;
205
+ this.detailCode = init.detailCode;
206
+ this.sessionId = init.sessionId;
207
+ }
208
+ /** `JSON.stringify(err)` 가 `{}` 가 되지 않도록 한다 (ApiError.toJSON 참조). */
209
+ toJSON() {
210
+ return {
211
+ name: this.name,
212
+ code: this.code,
213
+ message: this.message,
214
+ retryable: this.retryable,
215
+ status: this.status,
216
+ provider: this.provider,
217
+ model: this.model,
218
+ retryAfter: this.retryAfter,
219
+ detailCode: this.detailCode,
220
+ sessionId: this.sessionId
221
+ };
222
+ }
223
+ };
224
+ function isRetryableAICode(code) {
225
+ return code === "provider_timeout" || code === "service_unavailable" || code === "rate_limit_exceeded" || code === "stream_failed";
226
+ }
227
+ function toAIError(payload, fallback) {
228
+ if (payload instanceof AIError) return payload;
229
+ const p = typeof payload === "object" && payload !== null ? payload : {};
230
+ const str = (v) => typeof v === "string" && v !== "" ? v : void 0;
231
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
232
+ const documented = str(p.error) ?? str(p.code) ?? fallback?.code;
233
+ const sub = str(p.detail_code) ?? str(p.detailCode) ?? str(p.code);
234
+ const code = documented ?? "stream_failed";
235
+ const detailCode = sub && sub !== code ? sub : void 0;
236
+ const message = str(p.message) ?? (payload instanceof Error ? payload.message : void 0) ?? fallback?.message ?? code;
237
+ return new AIError({
238
+ code,
239
+ message,
240
+ retryable: typeof p.retryable === "boolean" ? p.retryable : void 0,
241
+ status: num(p.status) ?? num(p.statusCode) ?? fallback?.status,
242
+ provider: str(p.provider),
243
+ model: str(p.model),
244
+ retryAfter: num(p.retry_after_seconds) ?? num(p.retryAfter),
245
+ detailCode,
246
+ sessionId: str(p.session_id) ?? str(p.sessionId) ?? fallback?.sessionId
247
+ });
248
+ }
249
+ var AuthError = class extends Error {
250
+ constructor(message) {
251
+ super(message);
252
+ this.name = "AuthError";
253
+ }
254
+ };
255
+ var GameError = class extends Error {
256
+ constructor(init) {
257
+ super(init.message || init.code || "GameError");
258
+ this.name = "GameError";
259
+ this.code = init.code || "UNKNOWN";
260
+ this.phase = init.phase;
261
+ this.feature = init.feature;
262
+ this.roomId = init.roomId;
263
+ this.scriptId = init.scriptId;
264
+ this.originClientId = init.originClientId;
265
+ this.requested = init.requested;
266
+ this.available = init.available;
267
+ }
268
+ /** `JSON.stringify(err)` 가 `{}` 가 되지 않도록 한다 (ApiError.toJSON 참조). */
269
+ toJSON() {
270
+ return {
271
+ name: this.name,
272
+ code: this.code,
273
+ message: this.message,
274
+ phase: this.phase,
275
+ feature: this.feature,
276
+ roomId: this.roomId,
277
+ scriptId: this.scriptId,
278
+ originClientId: this.originClientId,
279
+ requested: this.requested,
280
+ available: this.available
281
+ };
282
+ }
283
+ };
284
+
165
285
  // src/api/ai.ts
166
286
  var AIAPI = class {
167
287
  constructor(http) {
@@ -226,13 +346,24 @@ var AIAPI = class {
226
346
  signal
227
347
  });
228
348
  if (!response.ok) {
229
- const errorData = await response.json().catch(() => ({ error: "Stream request failed" }));
230
- callbacks.onError?.(errorData.error || "Stream request failed");
349
+ const errorData = await response.json().catch(() => ({ error: "stream_failed" }));
350
+ callbacks.onError?.(
351
+ toAIError(errorData, {
352
+ message: "Stream request failed",
353
+ status: response.status
354
+ })
355
+ );
231
356
  return;
232
357
  }
233
358
  reader = response.body?.getReader();
234
359
  if (!reader) {
235
- callbacks.onError?.("ReadableStream not supported");
360
+ callbacks.onError?.(
361
+ new AIError({
362
+ code: "stream_failed",
363
+ message: "ReadableStream not supported",
364
+ retryable: false
365
+ })
366
+ );
236
367
  return;
237
368
  }
238
369
  const decoder = new TextDecoder();
@@ -253,9 +384,7 @@ var AIAPI = class {
253
384
  try {
254
385
  const event = JSON.parse(data);
255
386
  if (event.error) {
256
- callbacks.onError?.(
257
- event.message || event.error || "stream error"
258
- );
387
+ callbacks.onError?.(toAIError(event));
259
388
  return;
260
389
  }
261
390
  if (event.type === "sources" && event.sources) {
@@ -1240,34 +1369,48 @@ var AnalyticsAPI = class {
1240
1369
  }
1241
1370
  };
1242
1371
 
1243
- // src/types/error.ts
1244
- var ApiError = class extends Error {
1245
- constructor(statusCode, message, code, details) {
1246
- super(message);
1247
- this.statusCode = statusCode;
1248
- this.name = "ApiError";
1249
- this.code = code;
1250
- this.details = details;
1372
+ // src/api/app-members.ts
1373
+ var AppMembersAPI = class {
1374
+ constructor(http) {
1375
+ this.http = http;
1251
1376
  }
1252
- };
1253
- var AuthError = class extends Error {
1254
- constructor(message) {
1255
- super(message);
1256
- this.name = "AuthError";
1377
+ ensureServerAuth(method) {
1378
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
1379
+ throw new Error(
1380
+ `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.`
1381
+ );
1382
+ }
1257
1383
  }
1258
- };
1259
- var GameError = class extends Error {
1260
- constructor(init) {
1261
- super(init.message || init.code || "GameError");
1262
- this.name = "GameError";
1263
- this.code = init.code || "UNKNOWN";
1264
- this.phase = init.phase;
1265
- this.feature = init.feature;
1266
- this.roomId = init.roomId;
1267
- this.scriptId = init.scriptId;
1268
- this.originClientId = init.originClientId;
1269
- this.requested = init.requested;
1270
- this.available = init.available;
1384
+ /**
1385
+ * 멤버 목록을 조회한다 (이메일 포함). (management_scope: `app_member:read`)
1386
+ *
1387
+ * @param appId ID
1388
+ * @param options 페이지네이션 및 `search`(닉네임·이메일 부분 일치)
1389
+ */
1390
+ async list(appId, options = {}) {
1391
+ this.ensureServerAuth("list");
1392
+ const query = new URLSearchParams();
1393
+ if (options.page !== void 0) query.set("page", String(options.page));
1394
+ if (options.pageSize !== void 0)
1395
+ query.set("page_size", String(options.pageSize));
1396
+ const search = options.search?.trim();
1397
+ if (search) query.set("search", search);
1398
+ const qs = query.toString();
1399
+ return this.http.get(
1400
+ `/v1/apps/${appId}/app-members${qs ? `?${qs}` : ""}`
1401
+ );
1402
+ }
1403
+ /**
1404
+ * 앱 멤버 상세를 조회한다 (이메일 + 로그인 수단 포함). (management_scope: `app_member:read`)
1405
+ *
1406
+ * @param appId 앱 ID
1407
+ * @param memberId 멤버 ID
1408
+ */
1409
+ async get(appId, memberId) {
1410
+ this.ensureServerAuth("get");
1411
+ return this.http.get(
1412
+ `/v1/apps/${appId}/app-members/${memberId}`
1413
+ );
1271
1414
  }
1272
1415
  };
1273
1416
 
@@ -6794,9 +6937,16 @@ var RealtimeAPI = class {
6794
6937
  });
6795
6938
  this.pendingRequests.clear();
6796
6939
  this.subscriptions.clear();
6797
- this.streamSessions.forEach((session) => {
6940
+ this.streamSessions.forEach((session, sessionId) => {
6798
6941
  if (session.handlers.onError) {
6799
- session.handlers.onError(new Error("Connection closed"));
6942
+ session.handlers.onError(
6943
+ new AIError({
6944
+ code: "service_unavailable",
6945
+ 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.",
6946
+ retryable: true,
6947
+ sessionId
6948
+ })
6949
+ );
6800
6950
  }
6801
6951
  });
6802
6952
  this.streamSessions.clear();
@@ -7061,17 +7211,26 @@ var RealtimeAPI = class {
7061
7211
  signal
7062
7212
  });
7063
7213
  if (!response.ok) {
7064
- const errData = await response.json().catch(() => ({ error: "Stream request failed" }));
7214
+ const errData = await response.json().catch(() => ({ error: "stream_failed" }));
7065
7215
  handlers.onError?.(
7066
- new Error(
7067
- errData.error || errData.message || "Stream request failed"
7068
- )
7216
+ toAIError(errData, {
7217
+ message: "Stream request failed",
7218
+ sessionId,
7219
+ status: response.status
7220
+ })
7069
7221
  );
7070
7222
  return;
7071
7223
  }
7072
7224
  reader = response.body?.getReader();
7073
7225
  if (!reader) {
7074
- handlers.onError?.(new Error("ReadableStream not supported"));
7226
+ handlers.onError?.(
7227
+ new AIError({
7228
+ code: "stream_failed",
7229
+ message: "ReadableStream not supported",
7230
+ retryable: false,
7231
+ sessionId
7232
+ })
7233
+ );
7075
7234
  return;
7076
7235
  }
7077
7236
  const decoder = new TextDecoder();
@@ -7098,9 +7257,7 @@ var RealtimeAPI = class {
7098
7257
  try {
7099
7258
  const ev = JSON.parse(data);
7100
7259
  if (ev.error) {
7101
- handlers.onError?.(
7102
- new Error(ev.message || ev.error || "stream error")
7103
- );
7260
+ handlers.onError?.(toAIError(ev, { sessionId }));
7104
7261
  return;
7105
7262
  }
7106
7263
  if (ev.type === "tool_start") {
@@ -7144,7 +7301,14 @@ var RealtimeAPI = class {
7144
7301
  } catch (err) {
7145
7302
  const aborted = signal.aborted || err instanceof DOMException && err.name === "AbortError" || typeof err === "object" && err !== null && err.name === "AbortError";
7146
7303
  if (!aborted) {
7147
- handlers.onError?.(err instanceof Error ? err : new Error(String(err)));
7304
+ handlers.onError?.(
7305
+ new AIError({
7306
+ code: "service_unavailable",
7307
+ message: err instanceof Error ? err.message : String(err) || "AI \uC2A4\uD2B8\uB9AC\uBC0D \uC694\uCCAD\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
7308
+ retryable: true,
7309
+ sessionId
7310
+ })
7311
+ );
7148
7312
  }
7149
7313
  } finally {
7150
7314
  this.sseSessions.delete(sessionId);
@@ -7782,12 +7946,11 @@ var RealtimeAPI = class {
7782
7946
  break;
7783
7947
  }
7784
7948
  case "stream_error": {
7785
- const data = msg.data;
7786
7949
  if (msg.request_id) {
7787
7950
  for (const [sessionId, session] of this.streamSessions) {
7788
7951
  if (session.requestId === msg.request_id) {
7789
7952
  if (session.handlers.onError) {
7790
- session.handlers.onError(new Error(data.message));
7953
+ session.handlers.onError(toAIError(msg.data, { sessionId }));
7791
7954
  }
7792
7955
  this.streamSessions.delete(sessionId);
7793
7956
  break;
@@ -7860,9 +8023,16 @@ var RealtimeAPI = class {
7860
8023
  }
7861
8024
  this.ws = null;
7862
8025
  this._connectionId = null;
7863
- this.streamSessions.forEach((session) => {
8026
+ this.streamSessions.forEach((session, sessionId) => {
7864
8027
  if (session.handlers.onError) {
7865
- session.handlers.onError(new Error("Connection lost"));
8028
+ session.handlers.onError(
8029
+ new AIError({
8030
+ code: "service_unavailable",
8031
+ 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.",
8032
+ retryable: true,
8033
+ sessionId
8034
+ })
8035
+ );
7866
8036
  }
7867
8037
  });
7868
8038
  this.streamSessions.clear();
@@ -11319,13 +11489,26 @@ var HttpClient = class {
11319
11489
  this.emitError(err2);
11320
11490
  throw err2;
11321
11491
  }
11322
- const message = typeof rawError === "string" ? rawError : errorData.message || "Unknown error";
11323
- const legacyDetails = retryAfterSeconds !== void 0 ? { retry_after_seconds: retryAfterSeconds } : void 0;
11492
+ const flatMessage = typeof errorData.message === "string" && errorData.message !== "" ? errorData.message : void 0;
11493
+ const explicitCode = typeof errorData.code === "string" && errorData.code !== "" ? errorData.code : void 0;
11494
+ const errorIsCode = typeof rawError === "string" && /^[a-z][a-z0-9_]*$/.test(rawError);
11495
+ const message = flatMessage ?? (typeof rawError === "string" && rawError !== "" ? rawError : "Unknown error");
11496
+ const code = explicitCode ?? (errorIsCode ? rawError : void 0);
11497
+ const legacyDetails = {};
11498
+ if (retryAfterSeconds !== void 0) {
11499
+ legacyDetails.retry_after_seconds = retryAfterSeconds;
11500
+ }
11501
+ if (typeof errorData.provider === "string") {
11502
+ legacyDetails.provider = errorData.provider;
11503
+ }
11504
+ if (typeof errorData.model === "string") {
11505
+ legacyDetails.model = errorData.model;
11506
+ }
11324
11507
  const err = new ApiError(
11325
11508
  response.status,
11326
11509
  message,
11327
- void 0,
11328
- legacyDetails
11510
+ code,
11511
+ Object.keys(legacyDetails).length > 0 ? legacyDetails : void 0
11329
11512
  );
11330
11513
  this.emitError(err);
11331
11514
  throw err;
@@ -12209,6 +12392,7 @@ var ConnectBase = class {
12209
12392
  this.subscription = new SubscriptionAPI(this.http);
12210
12393
  this.push = new PushAPI(this.http);
12211
12394
  this.roles = new RolesAPI(this.http);
12395
+ this.appMembers = new AppMembersAPI(this.http);
12212
12396
  this.video = new VideoAPI(
12213
12397
  this.http,
12214
12398
  config.videoUrl || env("CB_VIDEO_URL") || DEFAULT_VIDEO_URL
@@ -12304,9 +12488,11 @@ var index_default = ConnectBase;
12304
12488
  // Annotate the CommonJS export names for ESM import in node:
12305
12489
  0 && (module.exports = {
12306
12490
  AIAPI,
12491
+ AIError,
12307
12492
  AUTH_MEMBER_ID_TOKEN,
12308
12493
  AdsAPI,
12309
12494
  ApiError,
12495
+ AppMembersAPI,
12310
12496
  AuthError,
12311
12497
  ConnectBase,
12312
12498
  EndpointAPI,
@@ -12322,5 +12508,6 @@ var index_default = ConnectBase;
12322
12508
  detectInAppBrowser,
12323
12509
  escapeToExternalBrowser,
12324
12510
  isWebTransportSupported,
12511
+ toAIError,
12325
12512
  toCreateRoomWire
12326
12513
  });