connectbase-client 3.46.0 → 3.48.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
@@ -139,6 +139,9 @@ var HttpClient = class {
139
139
  * 한 번 settle 되면 그 결과(메모리 적재 또는 미로그인)가 항상 반영되어 있다.
140
140
  */
141
141
  this.bootRestorePromise = null;
142
+ // OAuth 콜백 부트스트랩처럼 세션 힌트 유무와 무관하게 첫 인증 호출이 반드시 기다려야
143
+ // 하는 boot promise 인지 여부 (setBootRestorePromise 의 alwaysAwait 옵션).
144
+ this.bootRestoreAlwaysAwait = false;
142
145
  this.config = { ...config };
143
146
  this.storageKey = this.buildStorageKey();
144
147
  this.warnIfUnsafePersistence();
@@ -148,9 +151,14 @@ var HttpClient = class {
148
151
  * 페이지 진입 시 fire-and-forget 으로 시작된 cookie 복구 promise 를 SDK 가 등록한다.
149
152
  * 같은 promise 가 `prepareHeaders` 에서 await 되어, 첫 인증 호출이 cookie 복구
150
153
  * 완료 후 발화한다.
154
+ *
155
+ * options.alwaysAwait=true 면 세션 힌트가 없어도 첫 인증 호출이 이 promise 를
156
+ * 기다린다 — OAuth 리다이렉트 콜백처럼 "이 부트 promise 가 곧 세션을 만든다"는 것이
157
+ * 확실한 흐름용 (일반 cookie 복구는 힌트 기반 fast-path 적용).
151
158
  */
152
- setBootRestorePromise(p) {
159
+ setBootRestorePromise(p, options) {
153
160
  this.bootRestorePromise = p.catch(() => false);
161
+ this.bootRestoreAlwaysAwait = options?.alwaysAwait === true;
154
162
  }
155
163
  /** 최근 호출 ring buffer 스냅샷 (시간순). */
156
164
  getRecentCalls() {
@@ -179,11 +187,49 @@ var HttpClient = class {
179
187
  this.config.accessToken = accessToken;
180
188
  this.config.refreshToken = refreshToken;
181
189
  this.persistTokens();
190
+ this.markSessionHint();
182
191
  }
183
192
  clearTokens() {
184
193
  this.config.accessToken = void 0;
185
194
  this.config.refreshToken = void 0;
186
195
  this.removePersistedTokens();
196
+ this.clearSessionHint();
197
+ }
198
+ // ===== 세션 힌트 =====
199
+ //
200
+ // "이 origin 에서 이 앱으로 로그인한 적이 있다"는 boolean 마커 (토큰 아님 — 민감정보 없음).
201
+ // 부트 시 cookie 복구(`/v1/auth/re-issue`)를 첫 인증 호출이 기다릴지 판단하는 데 쓴다:
202
+ // - 힌트 있음 → 기존과 동일하게 대기 (로그인 세션의 첫 호출이 익명으로 나가는 것 방지).
203
+ // - 힌트 없음 → 대기 생략. 익명 방문자(공개 사이트 첫 방문 대부분)의 첫 조회가
204
+ // re-issue 왕복만큼 빨라진다. 복구 자체는 백그라운드로 계속 진행되므로, 드물게
205
+ // 힌트가 지워졌지만 cookie 세션이 살아있는 경우도 이후 호출부터 자동 회복된다.
206
+ //
207
+ // 세션을 만드는 모든 흐름(로그인/OAuth 콜백/토큰 refresh)이 setTokens 를 경유하므로
208
+ // 그곳에서 마킹하고, clearTokens(로그아웃)에서 지운다.
209
+ buildSessionHintKey() {
210
+ return `${this.buildStorageKey()}:has_session`;
211
+ }
212
+ markSessionHint() {
213
+ if (typeof window === "undefined") return;
214
+ try {
215
+ localStorage.setItem(this.buildSessionHintKey(), "1");
216
+ } catch {
217
+ }
218
+ }
219
+ clearSessionHint() {
220
+ if (typeof window === "undefined") return;
221
+ try {
222
+ localStorage.removeItem(this.buildSessionHintKey());
223
+ } catch {
224
+ }
225
+ }
226
+ hasSessionHint() {
227
+ if (typeof window === "undefined") return false;
228
+ try {
229
+ return localStorage.getItem(this.buildSessionHintKey()) === "1";
230
+ } catch {
231
+ return true;
232
+ }
187
233
  }
188
234
  /**
189
235
  * OAuth redirect callback 직후 호출되어 HttpOnly cookie 를 부트스트랩한다.
@@ -512,7 +558,7 @@ var HttpClient = class {
512
558
  if (credential) {
513
559
  headers.set("X-Public-Key", credential);
514
560
  }
515
- if (!config?.skipAuth && !this.config.accessToken && this.config.publicKey && typeof window !== "undefined" && this.bootRestorePromise) {
561
+ if (!config?.skipAuth && !this.config.accessToken && this.config.publicKey && typeof window !== "undefined" && this.bootRestorePromise && (this.bootRestoreAlwaysAwait || this.hasSessionHint())) {
516
562
  try {
517
563
  await this.bootRestorePromise;
518
564
  } catch {
@@ -1222,6 +1268,7 @@ var DatabaseAPI = class {
1222
1268
  const params = new URLSearchParams();
1223
1269
  if (options?.limit) params.append("limit", options.limit.toString());
1224
1270
  if (options?.offset) params.append("offset", options.offset.toString());
1271
+ if (options?.count === false) params.append("count", "false");
1225
1272
  const queryString = params.toString();
1226
1273
  const url = queryString ? `${prefix}/tables/${tableId}/data?${queryString}` : `${prefix}/tables/${tableId}/data`;
1227
1274
  return this.http.get(url);
@@ -1240,7 +1287,8 @@ var DatabaseAPI = class {
1240
1287
  limit: options.limit,
1241
1288
  offset: options.offset,
1242
1289
  select: options.select,
1243
- exclude: options.exclude
1290
+ exclude: options.exclude,
1291
+ count: options.count
1244
1292
  }
1245
1293
  );
1246
1294
  }
@@ -1404,7 +1452,8 @@ var DatabaseAPI = class {
1404
1452
  offset: options.offset,
1405
1453
  select: options.select,
1406
1454
  exclude: options.exclude,
1407
- populate: options.populate
1455
+ populate: options.populate,
1456
+ count: options.count
1408
1457
  }
1409
1458
  );
1410
1459
  }
@@ -5403,6 +5452,38 @@ var PaymentAPI = class {
5403
5452
  const prefix = this.getPublicPrefix();
5404
5453
  return this.http.get(`${prefix}/payments/orders/${orderId}`);
5405
5454
  }
5455
+ /**
5456
+ * 앱의 결제 내역 목록을 조회한다 (필터/페이지네이션 지원).
5457
+ *
5458
+ * 서버사이드 전용 — service_role(ctx.cbAdmin, management_scopes=["payment:read"]) 또는 콘솔 JWT
5459
+ * 인증이 필요하다. 재무 데이터이므로 Public Key(cb_pk_) 단독 브라우저 SDK 인스턴스로는 호출할 수
5460
+ * 없다. (다른 payment 메서드와 달리 /v1/public 이 아니라 앱 스코프 dual-auth 라우트를 쓴다.)
5461
+ *
5462
+ * @example
5463
+ * ```typescript
5464
+ * // 함수(service_role, management_scopes: ["payment:read"]) 안에서
5465
+ * const { payments, total } = await ctx.cbAdmin.payment.list(ctx.appId, { status: 'done', limit: 20 })
5466
+ * ```
5467
+ */
5468
+ async list(appId, options) {
5469
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
5470
+ throw new Error(
5471
+ 'cb.payment.list() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["payment: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 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.'
5472
+ );
5473
+ }
5474
+ const params = new URLSearchParams();
5475
+ if (options?.status) params.set("status", options.status);
5476
+ if (options?.startDate) params.set("start_date", options.startDate);
5477
+ if (options?.endDate) params.set("end_date", options.endDate);
5478
+ if (options?.limit !== void 0)
5479
+ params.set("limit", String(options.limit));
5480
+ if (options?.offset !== void 0)
5481
+ params.set("offset", String(options.offset));
5482
+ const qs = params.toString();
5483
+ return this.http.get(
5484
+ `/v1/apps/${appId}/payments${qs ? `?${qs}` : ""}`
5485
+ );
5486
+ }
5406
5487
  };
5407
5488
 
5408
5489
  // src/api/subscription.ts
@@ -6004,6 +6085,28 @@ var PushAPI = class {
6004
6085
  };
6005
6086
  return this.http.post(`/v1/apps/${appId}/push/send-to-topic`, body);
6006
6087
  }
6088
+ /**
6089
+ * 앱의 푸시 통계(도달/오픈율/클릭율, 디바이스/메시지/토픽 수)를 조회한다.
6090
+ *
6091
+ * 서버사이드 전용 — service_role(ctx.cbAdmin, management_scopes=["push:read"]) 또는 콘솔 JWT
6092
+ * 인증이 필요하다. sendToMembers/sendToTopic 과 동일한 위임 가드 — cb_pk_ 단독(브라우저) SDK
6093
+ * 인스턴스는 차단하고, 위임 Bearer(ctx.cbAdmin) 인스턴스는 허용한다.
6094
+ *
6095
+ * @example
6096
+ * ```typescript
6097
+ * // 함수(service_role, management_scopes: ["push:read"]) 안에서
6098
+ * const stats = await ctx.cbAdmin.push.getStats(ctx.appId)
6099
+ * console.log(stats.open_rate, stats.click_rate)
6100
+ * ```
6101
+ */
6102
+ async getStats(appId) {
6103
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6104
+ throw new Error(
6105
+ 'cb.push.getStats() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["push: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 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.'
6106
+ );
6107
+ }
6108
+ return this.http.get(`/v1/apps/${appId}/push/stats`);
6109
+ }
6007
6110
  // ============ Helper Methods ============
6008
6111
  /**
6009
6112
  * 브라우저 고유 ID 생성 (localStorage에 저장)
@@ -6064,6 +6167,72 @@ var PushAPI = class {
6064
6167
  }
6065
6168
  };
6066
6169
 
6170
+ // src/api/roles.ts
6171
+ var RolesAPI = class {
6172
+ constructor(http) {
6173
+ this.http = http;
6174
+ }
6175
+ ensureServerAuth(method) {
6176
+ if (this.http.hasPublicKey() && !this.http.hasJWT()) {
6177
+ throw new Error(
6178
+ `cb.roles.${method}() \uB294 \uCF58\uC194 JWT \uB610\uB294 service_role(ctx.cbAdmin, management_scopes: ["role:read" | "role:manage"]) \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 Functions \uD658\uACBD\uC5D0\uC11C \uC0AC\uC6A9\uD558\uC138\uC694.`
6179
+ );
6180
+ }
6181
+ }
6182
+ /** 앱의 역할 목록을 조회한다. (management_scope: `role:read`) */
6183
+ async list(appId) {
6184
+ this.ensureServerAuth("list");
6185
+ return this.http.get(`/v1/apps/${appId}/app-roles`);
6186
+ }
6187
+ /** 역할 상세(권한/할당 사용자 포함)를 조회한다. (management_scope: `role:read`) */
6188
+ async get(appId, roleId) {
6189
+ this.ensureServerAuth("get");
6190
+ return this.http.get(`/v1/apps/${appId}/app-roles/${roleId}`);
6191
+ }
6192
+ /** 역할을 생성한다. (management_scope: `role:manage`) */
6193
+ async create(appId, payload) {
6194
+ this.ensureServerAuth("create");
6195
+ return this.http.post(`/v1/apps/${appId}/app-roles`, {
6196
+ role_title: payload.title,
6197
+ role_description: payload.description,
6198
+ selected_permission_ids: payload.permissionIds ?? []
6199
+ });
6200
+ }
6201
+ /**
6202
+ * 역할을 수정한다 — **전체 동기화(replace)**. permissionIds / userIds 는 전달값으로 대체된다.
6203
+ * (management_scope: `role:manage`)
6204
+ */
6205
+ async update(appId, roleId, payload) {
6206
+ this.ensureServerAuth("update");
6207
+ await this.http.put(`/v1/apps/${appId}/app-roles/${roleId}`, {
6208
+ role_title: payload.title,
6209
+ role_description: payload.description,
6210
+ selected_permission_ids: payload.permissionIds,
6211
+ selected_user_ids: payload.userIds
6212
+ });
6213
+ }
6214
+ /**
6215
+ * 역할에 사용자를 할당한다 (제목/설명/권한 보존). EditRole 이 전체 동기화라, 현재 상세를 읽어
6216
+ * 나머지 필드를 유지한 채 사용자 목록만 교체해 PUT 한다. userIds 는 "이 역할을 가질 사용자
6217
+ * 전체"다 (추가가 아니라 동기화). (management_scope: `role:manage`)
6218
+ */
6219
+ async assign(appId, roleId, userIds) {
6220
+ this.ensureServerAuth("assign");
6221
+ const detail = await this.http.get(`/v1/apps/${appId}/app-roles/${roleId}`);
6222
+ await this.http.put(`/v1/apps/${appId}/app-roles/${roleId}`, {
6223
+ role_title: detail.role_name,
6224
+ role_description: detail.role_description,
6225
+ selected_permission_ids: detail.permission_item.map((p) => p.id),
6226
+ selected_user_ids: userIds
6227
+ });
6228
+ }
6229
+ /** 역할을 삭제한다. (management_scope: `role:manage`) */
6230
+ async delete(appId, roleId) {
6231
+ this.ensureServerAuth("delete");
6232
+ await this.http.delete(`/v1/apps/${appId}/app-roles/${roleId}`);
6233
+ }
6234
+ };
6235
+
6067
6236
  // src/api/video.ts
6068
6237
  var DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
6069
6238
  var VideoProcessingError = class extends Error {
@@ -11152,6 +11321,7 @@ var ConnectBase = class {
11152
11321
  this.payment = new PaymentAPI(this.http);
11153
11322
  this.subscription = new SubscriptionAPI(this.http);
11154
11323
  this.push = new PushAPI(this.http);
11324
+ this.roles = new RolesAPI(this.http);
11155
11325
  this.video = new VideoAPI(this.http, config.videoUrl || env("CB_VIDEO_URL") || DEFAULT_VIDEO_URL);
11156
11326
  this.game = new GameAPI(this.http, config.gameUrl || env("CB_GAME_URL") || DEFAULT_GAME_URL, config.appId);
11157
11327
  this.ads = new AdsAPI(this.http);
@@ -11166,7 +11336,7 @@ var ConnectBase = class {
11166
11336
  const shouldAutoRestore = config.autoRestoreSession ?? true;
11167
11337
  if (shouldAutoRestore && typeof window !== "undefined") {
11168
11338
  if (this.isOAuthRedirectCallbackUrl()) {
11169
- this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot());
11339
+ this.http.setBootRestorePromise(this.oauth.consumeRedirectCallbackOnBoot(), { alwaysAwait: true });
11170
11340
  } else if (!this.isOAuthCallbackUrl()) {
11171
11341
  this.http.setBootRestorePromise(this.http.tryRestoreSessionFromCookie());
11172
11342
  }
@@ -11246,6 +11416,7 @@ export {
11246
11416
  GameRoom,
11247
11417
  GameRoomTransport,
11248
11418
  NativeAPI,
11419
+ RolesAPI,
11249
11420
  SessionManager,
11250
11421
  VideoProcessingError,
11251
11422
  index_default as default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "3.46.0",
3
+ "version": "3.48.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",