@proofchain/sdk 2.17.0 → 2.20.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
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  CertificatesResource: () => CertificatesResource,
26
26
  ChannelsResource: () => ChannelsResource,
27
27
  CohortLeaderboardClient: () => CohortLeaderboardClient,
28
+ CredentialsClient: () => CredentialsClient,
28
29
  DataViewsClient: () => DataViewsClient,
29
30
  DocumentsResource: () => DocumentsResource,
30
31
  EndUserIngestionClient: () => EndUserIngestionClient,
@@ -1615,6 +1616,71 @@ var RewardsClient = class {
1615
1616
  async listAssets(definitionId) {
1616
1617
  return this.http.get(`/rewards/definitions/${definitionId}/assets`);
1617
1618
  }
1619
+ // ---------------------------------------------------------------------------
1620
+ // Reward Attestations
1621
+ // ---------------------------------------------------------------------------
1622
+ /**
1623
+ * Create a signed attestation for an earned reward.
1624
+ *
1625
+ * Binds the reward to the user's wallet with an EIP-712 signature.
1626
+ * The anchoring mode is determined by the reward definition or tenant config.
1627
+ *
1628
+ * @param earnedRewardId - The earned reward ID to attest
1629
+ * @returns The attestation details including signature and on-chain status
1630
+ *
1631
+ * @example
1632
+ * ```ts
1633
+ * const attestation = await client.rewards.attest('earned-reward-id');
1634
+ * console.log(attestation.attestation_uid, attestation.status);
1635
+ * ```
1636
+ */
1637
+ async attest(earnedRewardId) {
1638
+ return this.http.post(`/rewards/earned/${earnedRewardId}/attest`, {});
1639
+ }
1640
+ /**
1641
+ * Anchor an earned reward's attestation on-chain (on-demand).
1642
+ *
1643
+ * The reward must already have a signed attestation.
1644
+ * Writes the attestation hash to the Base blockchain contract.
1645
+ *
1646
+ * @param earnedRewardId - The earned reward ID whose attestation to anchor
1647
+ *
1648
+ * @example
1649
+ * ```ts
1650
+ * const result = await client.rewards.anchor('earned-reward-id');
1651
+ * console.log('Anchored on-chain:', result.tx_hash);
1652
+ * ```
1653
+ */
1654
+ async anchor(earnedRewardId) {
1655
+ return this.http.post(`/rewards/earned/${earnedRewardId}/anchor`, {});
1656
+ }
1657
+ /**
1658
+ * Get the attestation for an earned reward.
1659
+ *
1660
+ * @param earnedRewardId - The earned reward ID
1661
+ */
1662
+ async getAttestation(earnedRewardId) {
1663
+ return this.http.get(`/rewards/earned/${earnedRewardId}/attestation`);
1664
+ }
1665
+ /**
1666
+ * Verify a reward attestation's signature and on-chain status.
1667
+ *
1668
+ * @param earnedRewardId - The earned reward ID to verify
1669
+ *
1670
+ * @example
1671
+ * ```ts
1672
+ * const result = await client.rewards.verifyAttestation('earned-reward-id');
1673
+ * if (result.is_valid) {
1674
+ * console.log('Reward is cryptographically verified!');
1675
+ * if (result.on_chain) {
1676
+ * console.log('Also anchored on Base:', result.tx_hash);
1677
+ * }
1678
+ * }
1679
+ * ```
1680
+ */
1681
+ async verifyAttestation(earnedRewardId) {
1682
+ return this.http.get(`/rewards/earned/${earnedRewardId}/verify`);
1683
+ }
1618
1684
  };
1619
1685
 
1620
1686
  // src/quests.ts
@@ -1738,6 +1804,21 @@ var QuestsClient = class {
1738
1804
  async getUserProgress(questId, userId) {
1739
1805
  return this.http.get(`/quests/${questId}/progress/${encodeURIComponent(userId)}`);
1740
1806
  }
1807
+ /**
1808
+ * Mark a quest step as in-progress (started).
1809
+ *
1810
+ * Call this when the user clicks a CTA link to begin a challenge.
1811
+ * Sets the step to 'in_progress' status so the frontend can show
1812
+ * a pending state while waiting for the completion event to fire.
1813
+ *
1814
+ * Idempotent — safe to call multiple times for the same step.
1815
+ */
1816
+ async startStep(questId, userId, stepIndex) {
1817
+ return this.http.post(
1818
+ `/quests/${questId}/progress/${encodeURIComponent(userId)}/step/${stepIndex}/start`,
1819
+ {}
1820
+ );
1821
+ }
1741
1822
  /**
1742
1823
  * Complete a step manually by step index
1743
1824
  */
@@ -1756,7 +1837,7 @@ var QuestsClient = class {
1756
1837
  /**
1757
1838
  * Claim a completed quest reward.
1758
1839
  * Only applicable for quests with reward_mode='claimable'.
1759
- * Transitions the quest from REWARD_CLAIMABLE to REWARD_PENDING/COMPLETED
1840
+ * Transitions the quest from COMPLETED to CLAIMED
1760
1841
  * and awards points + creates the earned reward.
1761
1842
  */
1762
1843
  async claimReward(questId, userId) {
@@ -2153,6 +2234,7 @@ var FanpassLeaderboardClient = class {
2153
2234
  // src/notifications.ts
2154
2235
  var NotificationsClient = class {
2155
2236
  constructor(http) {
2237
+ this.http = http;
2156
2238
  this.baseUrl = http.baseUrl || "https://api.proofchain.co.za";
2157
2239
  this.authHeaders = () => {
2158
2240
  const headers = {};
@@ -2243,6 +2325,312 @@ var NotificationsClient = class {
2243
2325
  }
2244
2326
  };
2245
2327
  }
2328
+ // ===========================================================================
2329
+ // WEB PUSH (OS-level notifications, even when browser is closed)
2330
+ // ===========================================================================
2331
+ /**
2332
+ * Get the tenant's VAPID public key for Web Push.
2333
+ *
2334
+ * This key is needed to call `pushManager.subscribe()` in the browser.
2335
+ * Returns null if Web Push is not configured for this tenant.
2336
+ */
2337
+ async getVapidKey() {
2338
+ try {
2339
+ const res = await this.http.get("/notifications/push/vapid-key");
2340
+ return res.vapid_public_key;
2341
+ } catch {
2342
+ return null;
2343
+ }
2344
+ }
2345
+ /**
2346
+ * Register a browser push subscription with the server.
2347
+ *
2348
+ * Call this after the user grants notification permission and you
2349
+ * obtain a PushSubscription from `pushManager.subscribe()`.
2350
+ *
2351
+ * @param userId - External user ID
2352
+ * @param subscription - The PushSubscription object (or its JSON)
2353
+ * @returns Subscription ID from the server
2354
+ */
2355
+ async registerPush(userId, subscription) {
2356
+ const json = "toJSON" in subscription ? subscription.toJSON() : subscription;
2357
+ return this.http.post(
2358
+ "/notifications/push/subscribe",
2359
+ {
2360
+ user_id: userId,
2361
+ endpoint: json.endpoint,
2362
+ keys: json.keys,
2363
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
2364
+ }
2365
+ );
2366
+ }
2367
+ /**
2368
+ * Remove a push subscription from the server.
2369
+ *
2370
+ * Call this when the user logs out or revokes notification permission.
2371
+ *
2372
+ * @param userId - External user ID
2373
+ * @param endpoint - The push endpoint URL to remove
2374
+ */
2375
+ async unregisterPush(userId, endpoint) {
2376
+ return this.http.post(
2377
+ "/notifications/push/unsubscribe",
2378
+ { user_id: userId, endpoint }
2379
+ );
2380
+ }
2381
+ /**
2382
+ * High-level helper: request permission, subscribe to push, and register.
2383
+ *
2384
+ * Handles the full Web Push registration flow:
2385
+ * 1. Fetches the VAPID public key from the server
2386
+ * 2. Requests notification permission from the user
2387
+ * 3. Registers a Service Worker (if not already registered)
2388
+ * 4. Subscribes to push via the Push API
2389
+ * 5. Sends the subscription to the server
2390
+ *
2391
+ * @param userId - External user ID
2392
+ * @param serviceWorkerPath - Path to the service worker file (default: '/sw.js')
2393
+ * @returns The PushSubscription, or null if denied/unavailable
2394
+ *
2395
+ * @example
2396
+ * ```typescript
2397
+ * const sub = await client.notifications.requestPermissionAndSubscribe(
2398
+ * 'user-123',
2399
+ * '/proofchain-sw.js'
2400
+ * );
2401
+ * if (sub) {
2402
+ * console.log('Push notifications enabled!');
2403
+ * }
2404
+ * ```
2405
+ */
2406
+ async requestPermissionAndSubscribe(userId, serviceWorkerPath = "/sw.js") {
2407
+ if (typeof window === "undefined" || !("serviceWorker" in navigator) || !("PushManager" in window)) {
2408
+ console.warn("Web Push is not supported in this browser");
2409
+ return null;
2410
+ }
2411
+ const vapidKey = await this.getVapidKey();
2412
+ if (!vapidKey) {
2413
+ console.warn("Web Push not configured for this tenant");
2414
+ return null;
2415
+ }
2416
+ const permission = await Notification.requestPermission();
2417
+ if (permission !== "granted") {
2418
+ console.warn("Notification permission denied");
2419
+ return null;
2420
+ }
2421
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath);
2422
+ await navigator.serviceWorker.ready;
2423
+ const applicationServerKey = this._urlBase64ToUint8Array(vapidKey);
2424
+ const pushSubscription = await registration.pushManager.subscribe({
2425
+ userVisibleOnly: true,
2426
+ applicationServerKey: applicationServerKey.buffer
2427
+ });
2428
+ await this.registerPush(userId, pushSubscription);
2429
+ return pushSubscription;
2430
+ }
2431
+ /**
2432
+ * Generate a basic Service Worker script for handling push notifications.
2433
+ *
2434
+ * Returns JavaScript source code that can be saved as your sw.js file.
2435
+ * The generated worker handles `push` events (shows notification) and
2436
+ * `notificationclick` events (opens the app).
2437
+ *
2438
+ * @param defaultIcon - Optional default icon URL for notifications
2439
+ * @param defaultUrl - URL to open when notification is clicked (default: '/')
2440
+ * @returns Service Worker JavaScript source code as a string
2441
+ *
2442
+ * @example
2443
+ * ```typescript
2444
+ * // Save the output as /public/sw.js in your project
2445
+ * const swCode = client.notifications.generateServiceWorker({
2446
+ * defaultIcon: '/icon-192.png',
2447
+ * defaultUrl: '/',
2448
+ * });
2449
+ * ```
2450
+ */
2451
+ generateServiceWorker(options = {}) {
2452
+ const { defaultIcon = "/icon-192.png", defaultUrl = "/" } = options;
2453
+ return `// ProofChain Push Notification Service Worker
2454
+ // Auto-generated \u2014 place this file at the root of your public directory
2455
+
2456
+ self.addEventListener('push', function(event) {
2457
+ if (!event.data) return;
2458
+
2459
+ let payload;
2460
+ try {
2461
+ payload = event.data.json();
2462
+ } catch (e) {
2463
+ payload = { title: 'Notification', body: event.data.text() };
2464
+ }
2465
+
2466
+ const options = {
2467
+ body: payload.body || '',
2468
+ icon: payload.icon || '${defaultIcon}',
2469
+ badge: payload.badge || '${defaultIcon}',
2470
+ data: {
2471
+ url: payload.url || '${defaultUrl}',
2472
+ ...payload.data,
2473
+ },
2474
+ tag: payload.data?.event || 'proofchain',
2475
+ renotify: true,
2476
+ };
2477
+
2478
+ event.waitUntil(
2479
+ self.registration.showNotification(payload.title || 'Notification', options)
2480
+ );
2481
+ });
2482
+
2483
+ self.addEventListener('notificationclick', function(event) {
2484
+ event.notification.close();
2485
+ const url = event.notification.data?.url || '${defaultUrl}';
2486
+
2487
+ event.waitUntil(
2488
+ clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
2489
+ // Focus existing tab if open
2490
+ for (const client of clientList) {
2491
+ if (client.url === url && 'focus' in client) {
2492
+ return client.focus();
2493
+ }
2494
+ }
2495
+ // Otherwise open new tab
2496
+ return clients.openWindow(url);
2497
+ })
2498
+ );
2499
+ });
2500
+ `;
2501
+ }
2502
+ /** Convert a base64url-encoded VAPID key to a Uint8Array for the Push API. */
2503
+ _urlBase64ToUint8Array(base64String) {
2504
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
2505
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
2506
+ const rawData = atob(base64);
2507
+ const outputArray = new Uint8Array(rawData.length);
2508
+ for (let i = 0; i < rawData.length; ++i) {
2509
+ outputArray[i] = rawData.charCodeAt(i);
2510
+ }
2511
+ return outputArray;
2512
+ }
2513
+ };
2514
+
2515
+ // src/credentials.ts
2516
+ var CredentialsClient = class {
2517
+ constructor(http) {
2518
+ this.http = http;
2519
+ }
2520
+ // ---------------------------------------------------------------------------
2521
+ // Credential Types
2522
+ // ---------------------------------------------------------------------------
2523
+ /**
2524
+ * Create a new credential type
2525
+ */
2526
+ async createType(request) {
2527
+ return this.http.post("/credentials/types", request);
2528
+ }
2529
+ /**
2530
+ * List credential types
2531
+ */
2532
+ async listTypes(options) {
2533
+ return this.http.get("/credentials/types", {
2534
+ status: options?.status,
2535
+ category: options?.category
2536
+ });
2537
+ }
2538
+ /**
2539
+ * Get a specific credential type
2540
+ */
2541
+ async getType(typeId) {
2542
+ return this.http.get(`/credentials/types/${typeId}`);
2543
+ }
2544
+ /**
2545
+ * Update a credential type
2546
+ */
2547
+ async updateType(typeId, request) {
2548
+ return this.http.put(`/credentials/types/${typeId}`, request);
2549
+ }
2550
+ /**
2551
+ * Activate a credential type
2552
+ */
2553
+ async activateType(typeId) {
2554
+ return this.http.post(`/credentials/types/${typeId}/activate`, {});
2555
+ }
2556
+ /**
2557
+ * Archive a credential type
2558
+ */
2559
+ async archiveType(typeId) {
2560
+ return this.http.post(`/credentials/types/${typeId}/archive`, {});
2561
+ }
2562
+ // ---------------------------------------------------------------------------
2563
+ // Credential Issuance
2564
+ // ---------------------------------------------------------------------------
2565
+ /**
2566
+ * Issue a credential to a user
2567
+ */
2568
+ async issue(request) {
2569
+ return this.http.post("/credentials/issue", request);
2570
+ }
2571
+ /**
2572
+ * List issued credentials
2573
+ */
2574
+ async listIssued(options) {
2575
+ return this.http.get("/credentials/issued", {
2576
+ user_id: options?.user_id,
2577
+ credential_type_id: options?.credential_type_id,
2578
+ status: options?.status,
2579
+ limit: options?.limit,
2580
+ offset: options?.offset
2581
+ });
2582
+ }
2583
+ /**
2584
+ * Revoke an issued credential
2585
+ */
2586
+ async revoke(credentialId, reason) {
2587
+ const params = reason ? `?reason=${encodeURIComponent(reason)}` : "";
2588
+ return this.http.post(`/credentials/issued/${credentialId}/revoke${params}`, {});
2589
+ }
2590
+ /**
2591
+ * Suspend an issued credential
2592
+ */
2593
+ async suspend(credentialId, reason) {
2594
+ const params = reason ? `?reason=${encodeURIComponent(reason)}` : "";
2595
+ return this.http.post(`/credentials/issued/${credentialId}/suspend${params}`, {});
2596
+ }
2597
+ /**
2598
+ * Reinstate a suspended credential
2599
+ */
2600
+ async reinstate(credentialId) {
2601
+ return this.http.post(`/credentials/issued/${credentialId}/reinstate`, {});
2602
+ }
2603
+ // ---------------------------------------------------------------------------
2604
+ // User Management
2605
+ // ---------------------------------------------------------------------------
2606
+ /**
2607
+ * Opt a user in to identity credentials (tenant admin)
2608
+ */
2609
+ async optInUser(userExternalId) {
2610
+ return this.http.post(`/credentials/opt-in/${userExternalId}`, {});
2611
+ }
2612
+ /**
2613
+ * Opt a user out of identity credentials (tenant admin)
2614
+ */
2615
+ async optOutUser(userExternalId) {
2616
+ return this.http.post(`/credentials/opt-out/${userExternalId}`, {});
2617
+ }
2618
+ /**
2619
+ * Get all credentials for a user
2620
+ */
2621
+ async getUserCredentials(userExternalId) {
2622
+ return this.http.get(`/credentials/user/${userExternalId}`);
2623
+ }
2624
+ // ---------------------------------------------------------------------------
2625
+ // Public Verification (no auth needed)
2626
+ // ---------------------------------------------------------------------------
2627
+ /**
2628
+ * Verify a credential by its verification code.
2629
+ * This is a public endpoint — no authentication required.
2630
+ */
2631
+ async verify(verificationCode) {
2632
+ return this.http.get(`/credentials/verify/${verificationCode}`);
2633
+ }
2246
2634
  };
2247
2635
 
2248
2636
  // src/client.ts
@@ -2543,6 +2931,7 @@ var ProofChain = class _ProofChain {
2543
2931
  this.cohorts = new CohortLeaderboardClient(this.http);
2544
2932
  this.fanpassLeaderboard = new FanpassLeaderboardClient(this.http);
2545
2933
  this.notifications = new NotificationsClient(this.http);
2934
+ this.credentials = new CredentialsClient(this.http);
2546
2935
  }
2547
2936
  /**
2548
2937
  * Create a client for end-user JWT authentication (PWA/frontend use).
@@ -2877,6 +3266,7 @@ var EndUserIngestionClient = class {
2877
3266
  CertificatesResource,
2878
3267
  ChannelsResource,
2879
3268
  CohortLeaderboardClient,
3269
+ CredentialsClient,
2880
3270
  DataViewsClient,
2881
3271
  DocumentsResource,
2882
3272
  EndUserIngestionClient,