@pickleball/server-sdk 0.1.1 → 0.3.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/proxy.cjs CHANGED
@@ -22,6 +22,8 @@ var proxy_exports = {};
22
22
  __export(proxy_exports, {
23
23
  createStableIdempotencyKey: () => createStableIdempotencyKey,
24
24
  handleBootstrap: () => handleBootstrap,
25
+ handleDeviceHeartbeat: () => handleDeviceHeartbeat,
26
+ handleDeviceRegister: () => handleDeviceRegister,
25
27
  handleSessionAction: () => handleSessionAction,
26
28
  handleSessions: () => handleSessions
27
29
  });
@@ -48,6 +50,15 @@ var PickleballApiError = class extends PickleballLiveError {
48
50
  this.name = "PickleballApiError";
49
51
  }
50
52
  };
53
+ var PickleballPartnerApiError = class extends PickleballLiveError {
54
+ constructor(message, partnerCode, status, details = {}) {
55
+ super(message, "PARTNER_API_ERROR");
56
+ this.partnerCode = partnerCode;
57
+ this.status = status;
58
+ this.details = details;
59
+ this.name = "PickleballPartnerApiError";
60
+ }
61
+ };
51
62
  var PickleballHttpError = class extends PickleballLiveError {
52
63
  constructor(status) {
53
64
  super(`Request failed with HTTP ${status}`, "HTTP_ERROR");
@@ -276,6 +287,49 @@ function validateGrant(value) {
276
287
  config: validateRemoteConfig(data.config)
277
288
  };
278
289
  }
290
+ function validateCameraGrant(value) {
291
+ const data = record(value);
292
+ const base = validateGrant(data);
293
+ if (!/^rtmps?:\/\//i.test(base.serverUrl)) invalid();
294
+ if (data.status !== "scheduled" && data.status !== "live") invalid();
295
+ return {
296
+ ...base,
297
+ status: data.status,
298
+ matchRef: nullableString(data.matchRef),
299
+ courtRef: nullableString(data.courtRef),
300
+ watchUrl: nullableString(data.watchUrl),
301
+ convexUrl: nullableString(data.convexUrl),
302
+ issuedAt: finite(data.issuedAt),
303
+ expiresAt: finite(data.expiresAt)
304
+ };
305
+ }
306
+ function validateCameraSessionCreated(value) {
307
+ const data = record(value);
308
+ if (data.status !== "scheduled" && data.status !== "live" && data.status !== "ended") {
309
+ invalid();
310
+ }
311
+ if (data.visibility !== "public" && data.visibility !== "private") invalid();
312
+ if (typeof data.reused !== "boolean") invalid();
313
+ return {
314
+ id: nonblank(data.id),
315
+ status: data.status,
316
+ visibility: data.visibility,
317
+ quality: finite(data.quality),
318
+ deliveryMode: nonblank(data.deliveryMode),
319
+ matchRef: nullableString(data.matchRef),
320
+ courtRef: nullableString(data.courtRef),
321
+ playbackUrl: nullableString(data.playbackUrl),
322
+ whepUrl: nullableString(data.whepUrl),
323
+ watchUrl: nullableString(data.watchUrl),
324
+ reused: data.reused,
325
+ cameraGrant: validateCameraGrant(data.cameraGrant)
326
+ };
327
+ }
328
+ function validateLegacyEnd(value) {
329
+ const data = record(value);
330
+ if (data.ok !== true || data.status !== "ended") invalid();
331
+ return { status: "ended" };
332
+ }
279
333
  function validateRefresh(value) {
280
334
  const data = record(value);
281
335
  return {
@@ -313,6 +367,7 @@ function validateSession(value) {
313
367
  matchRef: nullableString(data.matchRef),
314
368
  playbackUrl: nullableString(data.playbackUrl),
315
369
  watchUrl: nullableString(data.watchUrl),
370
+ whepUrl: nullableString(data.whepUrl),
316
371
  scheduledAt: nullableNumber(data.scheduledAt),
317
372
  startedAt: nullableNumber(data.startedAt),
318
373
  endedAt: nullableNumber(data.endedAt),
@@ -343,6 +398,29 @@ function validateRecordings(value, sessionId) {
343
398
  if (!Array.isArray(value)) invalid();
344
399
  return value.map((item) => validateRecording(item, sessionId));
345
400
  }
401
+ function validateDeviceRegister(value) {
402
+ const data = record(value);
403
+ const heartbeatIntervalMs = finite(data.heartbeatIntervalMs);
404
+ if (heartbeatIntervalMs <= 0) invalid();
405
+ return {
406
+ deviceId: nonblank(data.deviceId),
407
+ heartbeatIntervalMs
408
+ };
409
+ }
410
+ function validateDeviceHeartbeat(value) {
411
+ const data = record(value);
412
+ if (data.controlSession === null || data.controlSession === void 0) {
413
+ return { controlSession: null };
414
+ }
415
+ const grant = record(data.controlSession);
416
+ return {
417
+ controlSession: {
418
+ id: nonblank(grant.id),
419
+ token: nonblank(grant.token),
420
+ convexUrl: nonblank(grant.convexUrl)
421
+ }
422
+ };
423
+ }
346
424
 
347
425
  // src/index.ts
348
426
  var TRANSIENT_STATUSES = /* @__PURE__ */ new Set([408, 429]);
@@ -478,7 +556,23 @@ async function readBoundedResponseText(response2) {
478
556
  }
479
557
  return new TextDecoder().decode(bytes);
480
558
  }
481
- async function decodeResponse(response2, apiKey, validate) {
559
+ function parsePartnerError(payload, status, apiKey) {
560
+ if (!isRecord(payload) || !("error" in payload)) {
561
+ return new PickleballInvalidResponseError();
562
+ }
563
+ if (isRecord(payload.error)) {
564
+ return parseSdkError(payload, status, apiKey);
565
+ }
566
+ if (typeof payload.error !== "string") return new PickleballInvalidResponseError();
567
+ const { error, code, ...details } = payload;
568
+ const safeMessage = apiKey === "" ? error : error.split(apiKey).join("[REDACTED]");
569
+ const partnerCode = typeof code === "string" && code.trim() !== "" ? code : `HTTP_${status}`;
570
+ return new PickleballPartnerApiError(safeMessage, partnerCode, status, details);
571
+ }
572
+ async function decodeResponse(response2, apiKey, validate, options = {
573
+ errorStyle: "sdk",
574
+ envelope: "data"
575
+ }) {
482
576
  if (response2.status >= 300 && response2.status < 400) {
483
577
  await response2.body?.cancel().catch(() => void 0);
484
578
  throw new PickleballHttpError(response2.status);
@@ -490,7 +584,10 @@ async function decodeResponse(response2, apiKey, validate) {
490
584
  } catch {
491
585
  throw new PickleballInvalidResponseError();
492
586
  }
493
- if (!response2.ok) throw parseSdkError(payload, response2.status, apiKey);
587
+ if (!response2.ok) {
588
+ throw options.errorStyle === "v1" ? parsePartnerError(payload, response2.status, apiKey) : parseSdkError(payload, response2.status, apiKey);
589
+ }
590
+ if (options.envelope === "raw") return validate(payload);
494
591
  if (!isRecord(payload) || !("data" in payload)) {
495
592
  throw new PickleballInvalidResponseError();
496
593
  }
@@ -498,7 +595,15 @@ async function decodeResponse(response2, apiKey, validate) {
498
595
  }
499
596
  function createPickleballLiveClient(options) {
500
597
  const baseUrl = validatedBaseUrl(options.baseUrl);
501
- const appId = requiredNonblank(options.appId, "appId");
598
+ const configuredAppId = options.appId === void 0 ? null : requiredNonblank(options.appId, "appId");
599
+ const requireAppId = () => {
600
+ if (configuredAppId === null) {
601
+ throw new PickleballConfigurationError(
602
+ "appId is required for /api/v2/sdk operations (apps/sessions/devices)"
603
+ );
604
+ }
605
+ return configuredAppId;
606
+ };
502
607
  const apiKey = requiredNonblank(options.apiKey, "apiKey");
503
608
  const fetchImplementation = options.fetch ?? globalThis.fetch;
504
609
  if (typeof fetchImplementation !== "function") {
@@ -511,7 +616,9 @@ function createPickleballLiveClient(options) {
511
616
  path,
512
617
  body,
513
618
  retryPolicy,
514
- validate
619
+ validate,
620
+ errorStyle = "sdk",
621
+ envelope = "data"
515
622
  }) {
516
623
  const retryLimit = retryPolicy === "transient" ? maxRetries : 0;
517
624
  for (let attempt = 0; attempt <= retryLimit; attempt += 1) {
@@ -532,14 +639,14 @@ function createPickleballLiveClient(options) {
532
639
  } : {
533
640
  accept: "application/json",
534
641
  "x-api-key": apiKey,
535
- "x-pickleball-app-id": appId
642
+ ...configuredAppId === null ? {} : { "x-pickleball-app-id": configuredAppId }
536
643
  },
537
644
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
538
645
  signal: controller.signal,
539
646
  redirect: "error"
540
647
  });
541
648
  try {
542
- return await decodeResponse(response2, apiKey, validate);
649
+ return await decodeResponse(response2, apiKey, validate, { errorStyle, envelope });
543
650
  } catch (error) {
544
651
  if (isTransientStatus(response2.status) && attempt < retryLimit && isRetryableResponseError(error)) {
545
652
  clearTimeout(timer);
@@ -551,7 +658,7 @@ function createPickleballLiveClient(options) {
551
658
  throw error;
552
659
  }
553
660
  } catch (error) {
554
- if (error instanceof PickleballApiError || error instanceof PickleballHttpError || error instanceof PickleballInvalidResponseError) {
661
+ if (error instanceof PickleballApiError || error instanceof PickleballPartnerApiError || error instanceof PickleballHttpError || error instanceof PickleballInvalidResponseError) {
555
662
  throw error;
556
663
  }
557
664
  if (attempt < retryLimit) {
@@ -570,54 +677,115 @@ function createPickleballLiveClient(options) {
570
677
  const post = (path, body, retryPolicy, validate) => request({ method: "POST", path, body, retryPolicy, validate });
571
678
  const get = (path, validate) => request({ method: "GET", path, retryPolicy: "transient", validate });
572
679
  const sessionPath = (sessionId) => `/api/v2/sdk/sessions/${encodeURIComponent(sessionId)}`;
680
+ const v1SessionPath = (sessionId) => `/api/v1/live-sessions/${encodeURIComponent(sessionId)}`;
681
+ const postV1 = (path, body, retryPolicy, validate, envelope = "data") => request({ method: "POST", path, body, retryPolicy, validate, errorStyle: "v1", envelope });
682
+ const getV1 = (path, validate) => request({ method: "GET", path, retryPolicy: "transient", validate, errorStyle: "v1" });
573
683
  return {
574
684
  apps: {
575
- bootstrap: (input) => post(
685
+ bootstrap: async (input) => post(
576
686
  "/api/v2/sdk/bootstrap",
577
- { ...input, appId },
687
+ { ...input, appId: requireAppId() },
578
688
  "transient",
579
689
  validateBootstrap
580
690
  )
581
691
  },
582
692
  sessions: {
583
- start: (input) => post(
693
+ start: async (input) => post(
584
694
  "/api/v2/sdk/sessions",
585
- { ...input, appId },
695
+ { ...input, appId: requireAppId() },
586
696
  "transient",
587
697
  validateGrant
588
698
  ),
589
- refresh: (sessionId, input) => post(
699
+ refresh: async (sessionId, input) => post(
590
700
  `${sessionPath(sessionId)}/refresh`,
591
- { ...input, appId },
701
+ { ...input, appId: requireAppId() },
592
702
  "never",
593
703
  validateRefresh
594
704
  ),
595
705
  end: async (input) => {
596
706
  await post(
597
707
  `${sessionPath(input.sessionId)}/end`,
598
- { ...input, appId },
708
+ { ...input, appId: requireAppId() },
599
709
  "transient",
600
710
  (value) => validateEnd(value, input.sessionId)
601
711
  );
602
712
  },
603
- publish: (sessionId) => post(
713
+ publish: async (sessionId) => post(
604
714
  `${sessionPath(sessionId)}/publish`,
605
- { sessionId, appId },
715
+ { sessionId, appId: requireAppId() },
606
716
  "transient",
607
717
  (value) => validatePublishState(value, sessionId)
608
718
  ),
609
- unpublish: (sessionId) => post(
719
+ unpublish: async (sessionId) => post(
610
720
  `${sessionPath(sessionId)}/unpublish`,
611
- { sessionId, appId },
721
+ { sessionId, appId: requireAppId() },
612
722
  "transient",
613
723
  (value) => validatePublishState(value, sessionId)
614
724
  ),
615
- get: (sessionId) => get(sessionPath(sessionId), validateSession),
616
- getRecordings: (sessionId) => get(
617
- `${sessionPath(sessionId)}/recordings`,
725
+ get: async (sessionId) => {
726
+ requireAppId();
727
+ return get(sessionPath(sessionId), validateSession);
728
+ },
729
+ getRecordings: async (sessionId) => {
730
+ requireAppId();
731
+ return get(
732
+ `${sessionPath(sessionId)}/recordings`,
733
+ (value) => validateRecordings(value, sessionId)
734
+ );
735
+ }
736
+ },
737
+ liveSessions: {
738
+ createCameraSession: (input) => postV1(
739
+ "/api/v1/live-sessions",
740
+ {
741
+ title: input.title,
742
+ matchRef: input.matchRef,
743
+ cameraGrant: true,
744
+ deliveryMode: "device-rtmp",
745
+ ...input.visibility === void 0 ? {} : { visibility: input.visibility },
746
+ ...input.description === void 0 ? {} : { description: input.description },
747
+ ...input.quality === void 0 ? {} : { quality: input.quality },
748
+ ...input.courtRef === void 0 ? {} : { courtRef: input.courtRef }
749
+ },
750
+ // Idempotent theo (host, matchRef): gọi lại chỉ cấp grant mới, an toàn để retry.
751
+ "transient",
752
+ validateCameraSessionCreated
753
+ ),
754
+ cameraGrant: (sessionId) => postV1(
755
+ `${v1SessionPath(sessionId)}/camera-grant`,
756
+ {},
757
+ "transient",
758
+ validateCameraGrant
759
+ ),
760
+ end: (sessionId) => postV1(
761
+ `${v1SessionPath(sessionId)}/end`,
762
+ {},
763
+ "transient",
764
+ validateLegacyEnd,
765
+ "raw"
766
+ ),
767
+ get: (sessionId) => getV1(v1SessionPath(sessionId), validateSession),
768
+ getRecordings: (sessionId) => getV1(
769
+ `${v1SessionPath(sessionId)}/recordings`,
618
770
  (value) => validateRecordings(value, sessionId)
619
771
  )
620
772
  },
773
+ devices: {
774
+ register: async (input) => post(
775
+ "/api/v2/sdk/devices/register",
776
+ { ...input, appId: requireAppId() },
777
+ "transient",
778
+ validateDeviceRegister
779
+ ),
780
+ heartbeat: async (input) => post(
781
+ "/api/v2/sdk/devices/heartbeat",
782
+ { ...input, appId: requireAppId() },
783
+ // Heartbeat có side effect trao token 1 lần — không retry để tránh
784
+ // nhận token đã bị đánh dấu delivered ở lần trước
785
+ "never",
786
+ validateDeviceHeartbeat
787
+ )
788
+ },
621
789
  webhooks: { verify: verifyWebhook }
622
790
  };
623
791
  }
@@ -752,8 +920,9 @@ function startBody(value) {
752
920
  "visibility",
753
921
  "consentVersion",
754
922
  "metadata",
755
- "standby"
756
- ]) || !nonblank2(value.externalSessionId, 256) || !nonblank2(value.title, 200) || !nonblank2(value.consentVersion, 100) || value.visibility !== void 0 && value.visibility !== "public" && value.visibility !== "private" || value.standby !== void 0 && typeof value.standby !== "boolean") {
923
+ "standby",
924
+ "quality"
925
+ ]) || !nonblank2(value.externalSessionId, 256) || !nonblank2(value.title, 200) || !nonblank2(value.consentVersion, 100) || value.visibility !== void 0 && value.visibility !== "public" && value.visibility !== "private" || value.standby !== void 0 && typeof value.standby !== "boolean" || value.quality !== void 0 && value.quality !== 720 && value.quality !== 1080) {
757
926
  throw new InvalidRequest();
758
927
  }
759
928
  if (value.metadata !== void 0) {
@@ -1009,10 +1178,139 @@ async function handleSessionAction(request, params, deps) {
1009
1178
  return null;
1010
1179
  });
1011
1180
  }
1181
+ var DEVICE_INFO_FIELDS = ["appVersion", "deviceName", "deviceModel", "osVersion"];
1182
+ var DEVICE_METRIC_NUMBER_FIELDS = [
1183
+ "ramFreeMb",
1184
+ "ramTotalMb",
1185
+ "storageFreeGb",
1186
+ "batteryPct",
1187
+ "batteryTempC",
1188
+ "thermal",
1189
+ "linkMbps",
1190
+ "rssi"
1191
+ ];
1192
+ var DEVICE_METRIC_STRING_FIELDS = ["netType", "connQuality"];
1193
+ function deviceInfoFields(value) {
1194
+ const info = {};
1195
+ for (const field of DEVICE_INFO_FIELDS) {
1196
+ const entry = value[field];
1197
+ if (entry === void 0) continue;
1198
+ if (!nonblank2(entry, 200)) throw new InvalidRequest();
1199
+ info[field] = entry;
1200
+ }
1201
+ return info;
1202
+ }
1203
+ function deviceRegisterBody(value) {
1204
+ if (!hasOnlyKeys(value, [
1205
+ "sdkVersion",
1206
+ "platform",
1207
+ "bundleId",
1208
+ "installationId",
1209
+ ...DEVICE_INFO_FIELDS
1210
+ ])) {
1211
+ throw new InvalidRequest();
1212
+ }
1213
+ const identity = bootstrapBody({
1214
+ sdkVersion: value.sdkVersion,
1215
+ platform: value.platform,
1216
+ bundleId: value.bundleId,
1217
+ installationId: value.installationId
1218
+ });
1219
+ return { ...identity, ...deviceInfoFields(value) };
1220
+ }
1221
+ function deviceMetricsFields(value) {
1222
+ if (value === void 0) return void 0;
1223
+ if (!isObject(value)) throw new InvalidRequest();
1224
+ if (!hasOnlyKeys(value, [...DEVICE_METRIC_NUMBER_FIELDS, ...DEVICE_METRIC_STRING_FIELDS])) {
1225
+ throw new InvalidRequest();
1226
+ }
1227
+ const metrics = {};
1228
+ for (const field of DEVICE_METRIC_NUMBER_FIELDS) {
1229
+ const entry = value[field];
1230
+ if (entry === void 0) continue;
1231
+ if (typeof entry !== "number" || !Number.isFinite(entry)) throw new InvalidRequest();
1232
+ metrics[field] = entry;
1233
+ }
1234
+ for (const field of DEVICE_METRIC_STRING_FIELDS) {
1235
+ const entry = value[field];
1236
+ if (entry === void 0) continue;
1237
+ if (!nonblank2(entry, 50)) throw new InvalidRequest();
1238
+ metrics[field] = entry;
1239
+ }
1240
+ return metrics;
1241
+ }
1242
+ function deviceHeartbeatBody(value) {
1243
+ if (!hasOnlyKeys(value, [
1244
+ "sdkVersion",
1245
+ "platform",
1246
+ "bundleId",
1247
+ "installationId",
1248
+ "agentState",
1249
+ "currentSessionId",
1250
+ "appVersion",
1251
+ "metrics"
1252
+ ])) {
1253
+ throw new InvalidRequest();
1254
+ }
1255
+ const identity = bootstrapBody({
1256
+ sdkVersion: value.sdkVersion,
1257
+ platform: value.platform,
1258
+ bundleId: value.bundleId,
1259
+ installationId: value.installationId
1260
+ });
1261
+ if (value.agentState !== void 0 && !nonblank2(value.agentState, 50)) {
1262
+ throw new InvalidRequest();
1263
+ }
1264
+ if (value.currentSessionId !== void 0 && value.currentSessionId !== null && !nonblank2(value.currentSessionId, 256)) {
1265
+ throw new InvalidRequest();
1266
+ }
1267
+ if (value.appVersion !== void 0 && !nonblank2(value.appVersion, 200)) {
1268
+ throw new InvalidRequest();
1269
+ }
1270
+ const metrics = deviceMetricsFields(value.metrics);
1271
+ return {
1272
+ ...identity,
1273
+ ...value.agentState === void 0 ? {} : { agentState: value.agentState },
1274
+ ...value.currentSessionId === void 0 ? {} : { currentSessionId: value.currentSessionId },
1275
+ ...value.appVersion === void 0 ? {} : { appVersion: value.appVersion },
1276
+ ...metrics === void 0 ? {} : { metrics }
1277
+ };
1278
+ }
1279
+ function deviceClientFor(deps) {
1280
+ const client = clientFor(deps);
1281
+ if (!client.devices) {
1282
+ throw new PickleballConfigurationError("Proxy client does not support devices");
1283
+ }
1284
+ return client.devices;
1285
+ }
1286
+ async function handleDeviceRegister(request, deps) {
1287
+ return execute(deps, async () => {
1288
+ const authorization = userAuthorization(request.headers);
1289
+ const attestation = appAttestation(request.headers);
1290
+ const identity = identityFromHeaders(request.headers);
1291
+ const input = deviceRegisterBody(await bodyObject(request));
1292
+ if (!identitiesMatch(identity, input)) throw new InvalidRequest();
1293
+ await authorize(deps, authorization, "device_register", identity, {}, attestation);
1294
+ return deviceClientFor(deps).register(input);
1295
+ });
1296
+ }
1297
+ async function handleDeviceHeartbeat(request, deps) {
1298
+ return execute(deps, async () => {
1299
+ const authorization = userAuthorization(request.headers);
1300
+ const attestation = appAttestation(request.headers);
1301
+ const identity = identityFromHeaders(request.headers);
1302
+ const input = deviceHeartbeatBody(await bodyObject(request));
1303
+ if (!identitiesMatch(identity, input)) throw new InvalidRequest();
1304
+ await authorize(deps, authorization, "device_heartbeat", identity, {}, attestation);
1305
+ return deviceClientFor(deps).heartbeat(input);
1306
+ });
1307
+ }
1012
1308
  // Annotate the CommonJS export names for ESM import in node:
1013
1309
  0 && (module.exports = {
1014
1310
  createStableIdempotencyKey,
1015
1311
  handleBootstrap,
1312
+ handleDeviceHeartbeat,
1313
+ handleDeviceRegister,
1016
1314
  handleSessionAction,
1017
1315
  handleSessions
1018
1316
  });
package/dist/proxy.d.cts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { PickleballLiveClient } from './index.cjs';
2
2
 
3
+ /**
4
+ * @deprecated Mô hình "proxy do đối tác host" (SDK v2, 6 route + authz gateway)
5
+ * được giữ để tương thích. Tích hợp mới dùng camera grant (ADR-0002):
6
+ * `createPickleballLiveClient().liveSessions.createCameraSession()` ở backend
7
+ * đối tác và `@pickleball/expo-sdk` chế độ grant trên app — không cần proxy.
8
+ */
9
+
3
10
  interface ProxyEnvironment {
4
11
  PICKLEBALL_API_BASE_URL: string;
5
12
  PICKLEBALL_APP_ID: string;
@@ -21,6 +28,7 @@ interface ProxyDependencies {
21
28
  type ProxyClient = {
22
29
  apps: Pick<PickleballLiveClient["apps"], "bootstrap">;
23
30
  sessions: Pick<PickleballLiveClient["sessions"], "start" | "refresh" | "end" | "publish" | "unpublish">;
31
+ devices?: Pick<PickleballLiveClient["devices"], "register" | "heartbeat">;
24
32
  };
25
33
  interface SessionActionParams {
26
34
  id: string;
@@ -35,5 +43,7 @@ declare function createStableIdempotencyKey(input: {
35
43
  declare function handleBootstrap(request: Request, deps: ProxyDependencies): Promise<Response>;
36
44
  declare function handleSessions(request: Request, deps: ProxyDependencies): Promise<Response>;
37
45
  declare function handleSessionAction(request: Request, params: SessionActionParams, deps: ProxyDependencies): Promise<Response>;
46
+ declare function handleDeviceRegister(request: Request, deps: ProxyDependencies): Promise<Response>;
47
+ declare function handleDeviceHeartbeat(request: Request, deps: ProxyDependencies): Promise<Response>;
38
48
 
39
- export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleSessionAction, handleSessions };
49
+ export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleDeviceHeartbeat, handleDeviceRegister, handleSessionAction, handleSessions };
package/dist/proxy.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { PickleballLiveClient } from './index.js';
2
2
 
3
+ /**
4
+ * @deprecated Mô hình "proxy do đối tác host" (SDK v2, 6 route + authz gateway)
5
+ * được giữ để tương thích. Tích hợp mới dùng camera grant (ADR-0002):
6
+ * `createPickleballLiveClient().liveSessions.createCameraSession()` ở backend
7
+ * đối tác và `@pickleball/expo-sdk` chế độ grant trên app — không cần proxy.
8
+ */
9
+
3
10
  interface ProxyEnvironment {
4
11
  PICKLEBALL_API_BASE_URL: string;
5
12
  PICKLEBALL_APP_ID: string;
@@ -21,6 +28,7 @@ interface ProxyDependencies {
21
28
  type ProxyClient = {
22
29
  apps: Pick<PickleballLiveClient["apps"], "bootstrap">;
23
30
  sessions: Pick<PickleballLiveClient["sessions"], "start" | "refresh" | "end" | "publish" | "unpublish">;
31
+ devices?: Pick<PickleballLiveClient["devices"], "register" | "heartbeat">;
24
32
  };
25
33
  interface SessionActionParams {
26
34
  id: string;
@@ -35,5 +43,7 @@ declare function createStableIdempotencyKey(input: {
35
43
  declare function handleBootstrap(request: Request, deps: ProxyDependencies): Promise<Response>;
36
44
  declare function handleSessions(request: Request, deps: ProxyDependencies): Promise<Response>;
37
45
  declare function handleSessionAction(request: Request, params: SessionActionParams, deps: ProxyDependencies): Promise<Response>;
46
+ declare function handleDeviceRegister(request: Request, deps: ProxyDependencies): Promise<Response>;
47
+ declare function handleDeviceHeartbeat(request: Request, deps: ProxyDependencies): Promise<Response>;
38
48
 
39
- export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleSessionAction, handleSessions };
49
+ export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleDeviceHeartbeat, handleDeviceRegister, handleSessionAction, handleSessions };