spaps-sdk 1.6.7 → 1.7.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
@@ -203,10 +203,11 @@ __export(index_exports, {
203
203
  TokenManager: () => TokenManager,
204
204
  WalletUtils: () => WalletUtils,
205
205
  WebSocketAuthHelper: () => WebSocketAuthHelper,
206
+ X402PaymentRequiredSDKError: () => X402PaymentRequiredSDKError,
206
207
  canAccessAdmin: () => canAccessAdmin,
207
208
  createBrowserClient: () => createBrowserClient,
208
209
  createPermissionChecker: () => createPermissionChecker,
209
- createSecureMessageRequestSchema: () => import_spaps_types.createSecureMessageRequestSchema,
210
+ createSecureMessageRequestSchema: () => import_spaps_types2.createSecureMessageRequestSchema,
210
211
  createServerClient: () => createServerClient,
211
212
  default: () => index_default,
212
213
  defaultPermissionChecker: () => defaultPermissionChecker,
@@ -216,14 +217,22 @@ __export(index_exports, {
216
217
  getUserRole: () => getUserRole,
217
218
  hasPermission: () => hasPermission,
218
219
  isAdminAccount: () => isAdminAccount,
219
- secureMessageMetadataSchema: () => import_spaps_types.secureMessageMetadataSchema,
220
- secureMessageSchema: () => import_spaps_types.secureMessageSchema,
220
+ isEnvelope: () => isEnvelope,
221
+ isErrorEnvelope: () => isErrorEnvelope,
222
+ isSuccessEnvelope: () => isSuccessEnvelope,
223
+ isX402PaymentRequired: () => import_spaps_types.isX402PaymentRequired,
224
+ isX402ResourceStatus: () => import_spaps_types.isX402ResourceStatus,
225
+ secureMessageMetadataSchema: () => import_spaps_types2.secureMessageMetadataSchema,
226
+ secureMessageSchema: () => import_spaps_types2.secureMessageSchema,
227
+ unwrapEnvelope: () => unwrapEnvelope,
228
+ unwrapNestedData: () => unwrapNestedData,
221
229
  verifyCryptoWebhookSignature: () => verifyCryptoWebhookSignature
222
230
  });
223
231
  module.exports = __toCommonJS(index_exports);
224
232
  var import_crypto = __toESM(require("crypto"));
225
233
  var import_axios = __toESM(require("axios"));
226
234
  var import_spaps_types = require("spaps-types");
235
+ var import_spaps_types2 = require("spaps-types");
227
236
  init_permissions();
228
237
 
229
238
  // src/role-hierarchy.ts
@@ -510,12 +519,23 @@ function appendSupportedIssueReportScope(q, scope) {
510
519
  }
511
520
  q.append("scope", scope);
512
521
  }
513
- var SPAPSClient = class {
522
+ var X402PaymentRequiredSDKError = class extends Error {
523
+ paymentRequiredHeader;
524
+ response;
525
+ constructor(paymentRequiredHeader, response) {
526
+ super("x402 payment required");
527
+ this.name = "X402PaymentRequiredSDKError";
528
+ this.paymentRequiredHeader = paymentRequiredHeader;
529
+ this.response = response;
530
+ }
531
+ };
532
+ var SPAPSClient = class _SPAPSClient {
514
533
  client;
515
534
  apiKey;
516
535
  accessToken;
517
536
  refreshToken;
518
537
  _isLocalMode = false;
538
+ headerProvider;
519
539
  unwrapApiResponse(response, fallback) {
520
540
  if (!response) {
521
541
  throw new Error(fallback);
@@ -532,6 +552,13 @@ var SPAPSClient = class {
532
552
  }
533
553
  return payload;
534
554
  }
555
+ skillEvalMutationConfig(options) {
556
+ const ifMatch = options?.ifMatch ?? options?.caseVersion;
557
+ if (ifMatch === void 0 || ifMatch === null || ifMatch === "") {
558
+ return void 0;
559
+ }
560
+ return { headers: { "If-Match": String(ifMatch) } };
561
+ }
535
562
  isAxiosResponse(value) {
536
563
  if (!value || typeof value !== "object") {
537
564
  return false;
@@ -551,6 +578,41 @@ var SPAPSClient = class {
551
578
  const record = value;
552
579
  return "success" in record && typeof record.success === "boolean";
553
580
  }
581
+ static isSdkManagedHeader(name) {
582
+ const normalized = name.toLowerCase();
583
+ return normalized === "authorization" || normalized === "x-api-key";
584
+ }
585
+ static hasHeader(headers, name) {
586
+ if (!headers) return false;
587
+ if (typeof headers.has === "function") {
588
+ return headers.has(name);
589
+ }
590
+ const normalized = name.toLowerCase();
591
+ return Object.keys(headers).some((key) => key.toLowerCase() === normalized);
592
+ }
593
+ static setHeader(headers, name, value) {
594
+ if (typeof headers.set === "function") {
595
+ headers.set(name, value);
596
+ return;
597
+ }
598
+ headers[name] = value;
599
+ }
600
+ static assertSafeHeaderValue(name, value) {
601
+ if (typeof value === "string" && /[\r\n]/.test(value)) {
602
+ throw new Error(`Invalid header value for ${name}: control characters not allowed`);
603
+ }
604
+ }
605
+ applyCustomHeaders(headers) {
606
+ const customHeaders = this.headerProvider?.();
607
+ if (!customHeaders) return;
608
+ for (const [key, value] of Object.entries(customHeaders)) {
609
+ if (_SPAPSClient.isSdkManagedHeader(key) || _SPAPSClient.hasHeader(headers, key)) {
610
+ continue;
611
+ }
612
+ _SPAPSClient.assertSafeHeaderValue(key, value);
613
+ _SPAPSClient.setHeader(headers, key, value);
614
+ }
615
+ }
554
616
  // Admin namespace for cleaner API
555
617
  admin = {
556
618
  createProduct: (productData) => this.createProduct(productData),
@@ -725,8 +787,43 @@ var SPAPSClient = class {
725
787
  return this.unwrapApiResponse(res, "Failed to reply to issue report");
726
788
  }
727
789
  };
790
+ /**
791
+ * Application-scoped short links for browser apps that need stable public URLs.
792
+ */
793
+ appLinks = {
794
+ /**
795
+ * Create a short link owned by the authenticated user.
796
+ */
797
+ create: async (payload) => {
798
+ const res = await this.client.post(
799
+ "/api/v1/app-links",
800
+ payload,
801
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
802
+ );
803
+ return this.unwrapApiResponse(res, "Failed to create app link");
804
+ },
805
+ /**
806
+ * Resolve a public short link for the active application.
807
+ */
808
+ get: async (username, slug, options) => {
809
+ const q = new URLSearchParams();
810
+ if (options?.track) q.set("track", "true");
811
+ const qs = q.toString();
812
+ const res = await this.client.get(
813
+ `/api/v1/app-links/${encodeURIComponent(username)}/${encodeURIComponent(slug)}${qs ? `?${qs}` : ""}`,
814
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
815
+ );
816
+ return this.unwrapApiResponse(res, "Failed to get app link");
817
+ }
818
+ };
819
+ static envVar(name) {
820
+ if (typeof process !== "undefined" && process.env) {
821
+ return process.env[name];
822
+ }
823
+ return void 0;
824
+ }
728
825
  constructor(config = {}) {
729
- const apiUrl = config.apiUrl || process.env.SPAPS_API_URL || process.env.NEXT_PUBLIC_SPAPS_API_URL;
826
+ const apiUrl = config.apiUrl || _SPAPSClient.envVar("SPAPS_API_URL") || _SPAPSClient.envVar("NEXT_PUBLIC_SPAPS_API_URL");
730
827
  const isBrowser = typeof window !== "undefined";
731
828
  let effectiveApiKey;
732
829
  if (config.publishableKey) {
@@ -739,7 +836,7 @@ var SPAPSClient = class {
739
836
  } else if (config.apiKey) {
740
837
  effectiveApiKey = config.apiKey;
741
838
  } else {
742
- effectiveApiKey = process.env.SPAPS_API_KEY || process.env.NEXT_PUBLIC_SPAPS_API_KEY;
839
+ effectiveApiKey = _SPAPSClient.envVar("SPAPS_API_KEY") || _SPAPSClient.envVar("NEXT_PUBLIC_SPAPS_API_KEY");
743
840
  }
744
841
  if (!apiUrl || apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) {
745
842
  this._isLocalMode = true;
@@ -748,6 +845,7 @@ var SPAPSClient = class {
748
845
  if (!this.apiKey && !this._isLocalMode) {
749
846
  console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
750
847
  }
848
+ this.headerProvider = config.headerProvider;
751
849
  this.client = import_axios.default.create({
752
850
  baseURL: apiUrl || "http://localhost:3301",
753
851
  timeout: config.timeout || 1e4,
@@ -757,11 +855,13 @@ var SPAPSClient = class {
757
855
  }
758
856
  });
759
857
  this.client.interceptors.request.use((config2) => {
760
- if (this.apiKey && !config2.headers["X-API-Key"]) {
761
- config2.headers["X-API-Key"] = this.apiKey;
858
+ config2.headers = config2.headers || {};
859
+ this.applyCustomHeaders(config2.headers);
860
+ if (this.apiKey && !_SPAPSClient.hasHeader(config2.headers, "X-API-Key")) {
861
+ _SPAPSClient.setHeader(config2.headers, "X-API-Key", this.apiKey);
762
862
  }
763
- if (this.accessToken && !config2.headers.Authorization) {
764
- config2.headers.Authorization = `Bearer ${this.accessToken}`;
863
+ if (this.accessToken && !_SPAPSClient.hasHeader(config2.headers, "Authorization")) {
864
+ _SPAPSClient.setHeader(config2.headers, "Authorization", `Bearer ${this.accessToken}`);
765
865
  }
766
866
  return config2;
767
867
  });
@@ -1063,6 +1163,7 @@ var SPAPSClient = class {
1063
1163
  reconcile: async (options = {}) => {
1064
1164
  const headers = {};
1065
1165
  if (options.reconToken) {
1166
+ _SPAPSClient.assertSafeHeaderValue("X-Recon-Token", options.reconToken);
1066
1167
  headers["X-Recon-Token"] = options.reconToken;
1067
1168
  }
1068
1169
  const payload = {};
@@ -1288,6 +1389,176 @@ var SPAPSClient = class {
1288
1389
  return this.unwrapApiResponse(res, "Failed to disconnect");
1289
1390
  }
1290
1391
  };
1392
+ /**
1393
+ * x402 paid-resource namespace.
1394
+ * Handles resource status checks, payment-gated actions, receipts, and handoff authorization.
1395
+ */
1396
+ x402 = {
1397
+ getResourceStatus: async (resourceKey) => {
1398
+ const res = await this.client.get(
1399
+ `/api/x402/resources/${encodeURIComponent(resourceKey)}/status`
1400
+ );
1401
+ return this.unwrapApiResponse(res, "Failed to get x402 resource status");
1402
+ },
1403
+ executeAction: async (resourceKey, actionKey, options) => {
1404
+ const headers = {};
1405
+ if (this.accessToken) {
1406
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1407
+ }
1408
+ if (options?.paymentSignature) {
1409
+ headers["PAYMENT-SIGNATURE"] = options.paymentSignature;
1410
+ }
1411
+ const body = {};
1412
+ if (options?.target) body.target = options.target;
1413
+ const bridgeToken = options?.bridgeToken ?? options?.bridge_token;
1414
+ if (bridgeToken !== void 0) body.bridge_token = bridgeToken;
1415
+ const res = await this.client.post(
1416
+ `/api/x402/resources/${encodeURIComponent(resourceKey)}/actions/${encodeURIComponent(actionKey)}`,
1417
+ body,
1418
+ { headers, validateStatus: (s) => s < 500 }
1419
+ );
1420
+ const paymentRequiredHeader = res.headers?.["payment-required"] || res.headers?.["PAYMENT-REQUIRED"] || "";
1421
+ if (res.status === 402 && paymentRequiredHeader) {
1422
+ throw new X402PaymentRequiredSDKError(
1423
+ paymentRequiredHeader,
1424
+ res.data
1425
+ );
1426
+ }
1427
+ return this.unwrapApiResponse(res, "Failed to execute x402 action");
1428
+ },
1429
+ getReceipt: async (receiptId) => {
1430
+ const res = await this.client.get(`/api/x402/receipts/${encodeURIComponent(receiptId)}`);
1431
+ return this.unwrapApiResponse(res, "Failed to get x402 receipt");
1432
+ },
1433
+ listReceipts: async (params) => {
1434
+ const q = new URLSearchParams();
1435
+ if (params?.resourceKey) q.append("resource_key", params.resourceKey);
1436
+ if (params?.limit !== void 0) q.append("limit", String(params.limit));
1437
+ if (params?.offset !== void 0) q.append("offset", String(params.offset));
1438
+ const qs = q.toString();
1439
+ const res = await this.client.get(`/api/x402/receipts${qs ? `?${qs}` : ""}`);
1440
+ return this.unwrapApiResponse(res, "Failed to list x402 receipts");
1441
+ },
1442
+ verifyHandoff: async (token, target, bridgeToken, options) => {
1443
+ const body = {
1444
+ token,
1445
+ resource_key: options.resourceKey,
1446
+ action_key: options.actionKey,
1447
+ target,
1448
+ bridge_token: bridgeToken
1449
+ };
1450
+ const res = await this.client.post("/api/x402/handoff/verify", body);
1451
+ return this.unwrapApiResponse(res, "Failed to verify x402 handoff");
1452
+ }
1453
+ };
1454
+ /**
1455
+ * Blind comparative skill-eval namespace.
1456
+ */
1457
+ skillEvals = {
1458
+ createCase: async (payload, options) => {
1459
+ const headers = {};
1460
+ if (this.accessToken) {
1461
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1462
+ }
1463
+ if (options?.paymentSignature) {
1464
+ headers["PAYMENT-SIGNATURE"] = options.paymentSignature;
1465
+ }
1466
+ const res = await this.client.post("/api/skill-evals/cases", payload, {
1467
+ headers,
1468
+ validateStatus: (s) => s < 500
1469
+ });
1470
+ const paymentRequiredHeader = res.headers?.["payment-required"] || res.headers?.["PAYMENT-REQUIRED"] || "";
1471
+ if (res.status === 402 && paymentRequiredHeader) {
1472
+ throw new X402PaymentRequiredSDKError(paymentRequiredHeader, res.data);
1473
+ }
1474
+ return this.unwrapApiResponse(
1475
+ res,
1476
+ "Failed to create skill eval case"
1477
+ );
1478
+ },
1479
+ getCase: async (caseId) => {
1480
+ const res = await this.client.get(
1481
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}`
1482
+ );
1483
+ return this.unwrapApiResponse(res, "Failed to get skill eval case");
1484
+ },
1485
+ getReviewRoom: async (caseId) => {
1486
+ const res = await this.client.get(
1487
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}/room`
1488
+ );
1489
+ return this.unwrapApiResponse(res, "Failed to get skill eval room");
1490
+ },
1491
+ submitReview: async (caseId, payload, options) => {
1492
+ const config = this.skillEvalMutationConfig(options);
1493
+ const res = await this.client.post(
1494
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}/reviews`,
1495
+ payload,
1496
+ ...config ? [config] : []
1497
+ );
1498
+ return this.unwrapApiResponse(
1499
+ res,
1500
+ "Failed to submit skill eval review"
1501
+ );
1502
+ },
1503
+ respondToReview: async (caseId, reviewId, payload, options) => {
1504
+ const config = this.skillEvalMutationConfig(options);
1505
+ const res = await this.client.post(
1506
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}/reviews/${encodeURIComponent(reviewId)}/response`,
1507
+ payload,
1508
+ ...config ? [config] : []
1509
+ );
1510
+ return this.unwrapApiResponse(
1511
+ res,
1512
+ "Failed to respond to skill eval review"
1513
+ );
1514
+ },
1515
+ lockReviews: async (caseId, options) => {
1516
+ const config = this.skillEvalMutationConfig(options);
1517
+ const res = await this.client.post(
1518
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}/lock`,
1519
+ {},
1520
+ ...config ? [config] : []
1521
+ );
1522
+ return this.unwrapApiResponse(
1523
+ res,
1524
+ "Failed to lock skill eval reviews"
1525
+ );
1526
+ },
1527
+ revealEvidence: async (caseId, payload, options) => {
1528
+ const config = this.skillEvalMutationConfig(options);
1529
+ const res = await this.client.post(
1530
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}/reveal`,
1531
+ payload,
1532
+ ...config ? [config] : []
1533
+ );
1534
+ return this.unwrapApiResponse(
1535
+ res,
1536
+ "Failed to reveal skill eval evidence"
1537
+ );
1538
+ },
1539
+ createGovernanceSnapshot: async (caseId, payload, options) => {
1540
+ const config = this.skillEvalMutationConfig(options);
1541
+ const res = await this.client.post(
1542
+ `/api/skill-evals/cases/${encodeURIComponent(caseId)}/governance-snapshots`,
1543
+ payload,
1544
+ ...config ? [config] : []
1545
+ );
1546
+ return this.unwrapApiResponse(
1547
+ res,
1548
+ "Failed to create skill eval governance snapshot"
1549
+ );
1550
+ },
1551
+ importGovernanceOutcome: async (snapshotId, payload) => {
1552
+ const res = await this.client.post(
1553
+ `/api/skill-evals/governance-snapshots/${encodeURIComponent(snapshotId)}/outcome`,
1554
+ payload
1555
+ );
1556
+ return this.unwrapApiResponse(
1557
+ res,
1558
+ "Failed to import skill eval governance outcome"
1559
+ );
1560
+ }
1561
+ };
1291
1562
  /**
1292
1563
  * DayRate (Dynamic Scheduling) namespace
1293
1564
  * For booking half-day sessions with dynamic pricing
@@ -1316,6 +1587,21 @@ var SPAPSClient = class {
1316
1587
  createMultiBooking: async (payload) => {
1317
1588
  const res = await this.client.post("/api/dayrate/book-multi", payload);
1318
1589
  return this.unwrapApiResponse(res, "Failed to create multi-booking");
1590
+ },
1591
+ /**
1592
+ * Create a single-slot booking hold backed by an x402 paid-resource action.
1593
+ */
1594
+ createX402Booking: async (payload) => {
1595
+ const res = await this.client.post("/api/dayrate/book-x402", payload);
1596
+ return this.unwrapApiResponse(res, "Failed to create x402 booking");
1597
+ },
1598
+ /**
1599
+ * Get guest-safe checkout confirmation state for a Stripe session.
1600
+ */
1601
+ getCheckoutStatus: async (sessionId) => {
1602
+ const query = new URLSearchParams({ sessionId }).toString();
1603
+ const res = await this.client.get(`/api/dayrate/checkout-status?${query}`);
1604
+ return this.unwrapApiResponse(res, "Failed to get checkout status");
1319
1605
  }
1320
1606
  };
1321
1607
  // Stripe Methods
@@ -1604,13 +1890,24 @@ var TokenManager = class _TokenManager {
1604
1890
  s.removeItem(_TokenManager.USER_KEY);
1605
1891
  }
1606
1892
  }
1893
+ static decodePayload(token) {
1894
+ try {
1895
+ const parts = token.split(".");
1896
+ if (parts.length !== 3 || !parts[1]) return null;
1897
+ const decoded = JSON.parse(_TokenManager.base64Decode(parts[1]));
1898
+ if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return null;
1899
+ return decoded;
1900
+ } catch {
1901
+ return null;
1902
+ }
1903
+ }
1607
1904
  static isTokenExpired(token) {
1608
1905
  try {
1609
1906
  const parts = token.split(".");
1610
1907
  if (parts.length !== 3 || !parts[1]) return true;
1611
1908
  const payload = JSON.parse(_TokenManager.base64Decode(parts[1]));
1612
1909
  const now = Math.floor(Date.now() / 1e3);
1613
- return payload.exp < now;
1910
+ return typeof payload?.exp !== "number" || payload.exp < now;
1614
1911
  } catch {
1615
1912
  return true;
1616
1913
  }
@@ -1725,6 +2022,39 @@ var WalletUtils = class _WalletUtils {
1725
2022
  }
1726
2023
  }
1727
2024
  };
2025
+ function isEnvelope(value) {
2026
+ if (!value || typeof value !== "object") return false;
2027
+ const r = value;
2028
+ return "success" in r && typeof r.success === "boolean";
2029
+ }
2030
+ function isSuccessEnvelope(value) {
2031
+ return isEnvelope(value) && value.success === true;
2032
+ }
2033
+ function isErrorEnvelope(value) {
2034
+ if (!isEnvelope(value) || value.success !== false) return false;
2035
+ const error = value.error;
2036
+ if (!error || typeof error !== "object") return false;
2037
+ const r = error;
2038
+ return typeof r.code === "string" && typeof r.message === "string";
2039
+ }
2040
+ function unwrapEnvelope(value, fallbackMessage) {
2041
+ if (!isEnvelope(value)) return value;
2042
+ if (value.success === false) {
2043
+ const message = isErrorEnvelope(value) ? value.error.message : void 0;
2044
+ throw new Error(message || fallbackMessage || "SPAPS request failed");
2045
+ }
2046
+ return value.data;
2047
+ }
2048
+ function unwrapNestedData(value) {
2049
+ if (isEnvelope(value) && value.success !== false) {
2050
+ const inner = value.data;
2051
+ if (inner && typeof inner === "object" && "data" in inner) {
2052
+ return inner.data;
2053
+ }
2054
+ return inner;
2055
+ }
2056
+ return value;
2057
+ }
1728
2058
  function createBrowserClient(publishableKey, options) {
1729
2059
  if (!publishableKey.startsWith("spaps_pub_")) {
1730
2060
  console.warn("\u26A0\uFE0F SPAPS: Expected a publishable key (spaps_pub_xxx). Using a secret key in browser is not recommended.");
@@ -1759,6 +2089,7 @@ function detectKeyType(key) {
1759
2089
  TokenManager,
1760
2090
  WalletUtils,
1761
2091
  WebSocketAuthHelper,
2092
+ X402PaymentRequiredSDKError,
1762
2093
  canAccessAdmin,
1763
2094
  createBrowserClient,
1764
2095
  createPermissionChecker,
@@ -1771,7 +2102,14 @@ function detectKeyType(key) {
1771
2102
  getUserRole,
1772
2103
  hasPermission,
1773
2104
  isAdminAccount,
2105
+ isEnvelope,
2106
+ isErrorEnvelope,
2107
+ isSuccessEnvelope,
2108
+ isX402PaymentRequired,
2109
+ isX402ResourceStatus,
1774
2110
  secureMessageMetadataSchema,
1775
2111
  secureMessageSchema,
2112
+ unwrapEnvelope,
2113
+ unwrapNestedData,
1776
2114
  verifyCryptoWebhookSignature
1777
2115
  });