@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.mjs CHANGED
@@ -1559,6 +1559,71 @@ var RewardsClient = class {
1559
1559
  async listAssets(definitionId) {
1560
1560
  return this.http.get(`/rewards/definitions/${definitionId}/assets`);
1561
1561
  }
1562
+ // ---------------------------------------------------------------------------
1563
+ // Reward Attestations
1564
+ // ---------------------------------------------------------------------------
1565
+ /**
1566
+ * Create a signed attestation for an earned reward.
1567
+ *
1568
+ * Binds the reward to the user's wallet with an EIP-712 signature.
1569
+ * The anchoring mode is determined by the reward definition or tenant config.
1570
+ *
1571
+ * @param earnedRewardId - The earned reward ID to attest
1572
+ * @returns The attestation details including signature and on-chain status
1573
+ *
1574
+ * @example
1575
+ * ```ts
1576
+ * const attestation = await client.rewards.attest('earned-reward-id');
1577
+ * console.log(attestation.attestation_uid, attestation.status);
1578
+ * ```
1579
+ */
1580
+ async attest(earnedRewardId) {
1581
+ return this.http.post(`/rewards/earned/${earnedRewardId}/attest`, {});
1582
+ }
1583
+ /**
1584
+ * Anchor an earned reward's attestation on-chain (on-demand).
1585
+ *
1586
+ * The reward must already have a signed attestation.
1587
+ * Writes the attestation hash to the Base blockchain contract.
1588
+ *
1589
+ * @param earnedRewardId - The earned reward ID whose attestation to anchor
1590
+ *
1591
+ * @example
1592
+ * ```ts
1593
+ * const result = await client.rewards.anchor('earned-reward-id');
1594
+ * console.log('Anchored on-chain:', result.tx_hash);
1595
+ * ```
1596
+ */
1597
+ async anchor(earnedRewardId) {
1598
+ return this.http.post(`/rewards/earned/${earnedRewardId}/anchor`, {});
1599
+ }
1600
+ /**
1601
+ * Get the attestation for an earned reward.
1602
+ *
1603
+ * @param earnedRewardId - The earned reward ID
1604
+ */
1605
+ async getAttestation(earnedRewardId) {
1606
+ return this.http.get(`/rewards/earned/${earnedRewardId}/attestation`);
1607
+ }
1608
+ /**
1609
+ * Verify a reward attestation's signature and on-chain status.
1610
+ *
1611
+ * @param earnedRewardId - The earned reward ID to verify
1612
+ *
1613
+ * @example
1614
+ * ```ts
1615
+ * const result = await client.rewards.verifyAttestation('earned-reward-id');
1616
+ * if (result.is_valid) {
1617
+ * console.log('Reward is cryptographically verified!');
1618
+ * if (result.on_chain) {
1619
+ * console.log('Also anchored on Base:', result.tx_hash);
1620
+ * }
1621
+ * }
1622
+ * ```
1623
+ */
1624
+ async verifyAttestation(earnedRewardId) {
1625
+ return this.http.get(`/rewards/earned/${earnedRewardId}/verify`);
1626
+ }
1562
1627
  };
1563
1628
 
1564
1629
  // src/quests.ts
@@ -1682,6 +1747,21 @@ var QuestsClient = class {
1682
1747
  async getUserProgress(questId, userId) {
1683
1748
  return this.http.get(`/quests/${questId}/progress/${encodeURIComponent(userId)}`);
1684
1749
  }
1750
+ /**
1751
+ * Mark a quest step as in-progress (started).
1752
+ *
1753
+ * Call this when the user clicks a CTA link to begin a challenge.
1754
+ * Sets the step to 'in_progress' status so the frontend can show
1755
+ * a pending state while waiting for the completion event to fire.
1756
+ *
1757
+ * Idempotent — safe to call multiple times for the same step.
1758
+ */
1759
+ async startStep(questId, userId, stepIndex) {
1760
+ return this.http.post(
1761
+ `/quests/${questId}/progress/${encodeURIComponent(userId)}/step/${stepIndex}/start`,
1762
+ {}
1763
+ );
1764
+ }
1685
1765
  /**
1686
1766
  * Complete a step manually by step index
1687
1767
  */
@@ -1700,7 +1780,7 @@ var QuestsClient = class {
1700
1780
  /**
1701
1781
  * Claim a completed quest reward.
1702
1782
  * Only applicable for quests with reward_mode='claimable'.
1703
- * Transitions the quest from REWARD_CLAIMABLE to REWARD_PENDING/COMPLETED
1783
+ * Transitions the quest from COMPLETED to CLAIMED
1704
1784
  * and awards points + creates the earned reward.
1705
1785
  */
1706
1786
  async claimReward(questId, userId) {
@@ -2097,6 +2177,7 @@ var FanpassLeaderboardClient = class {
2097
2177
  // src/notifications.ts
2098
2178
  var NotificationsClient = class {
2099
2179
  constructor(http) {
2180
+ this.http = http;
2100
2181
  this.baseUrl = http.baseUrl || "https://api.proofchain.co.za";
2101
2182
  this.authHeaders = () => {
2102
2183
  const headers = {};
@@ -2187,6 +2268,312 @@ var NotificationsClient = class {
2187
2268
  }
2188
2269
  };
2189
2270
  }
2271
+ // ===========================================================================
2272
+ // WEB PUSH (OS-level notifications, even when browser is closed)
2273
+ // ===========================================================================
2274
+ /**
2275
+ * Get the tenant's VAPID public key for Web Push.
2276
+ *
2277
+ * This key is needed to call `pushManager.subscribe()` in the browser.
2278
+ * Returns null if Web Push is not configured for this tenant.
2279
+ */
2280
+ async getVapidKey() {
2281
+ try {
2282
+ const res = await this.http.get("/notifications/push/vapid-key");
2283
+ return res.vapid_public_key;
2284
+ } catch {
2285
+ return null;
2286
+ }
2287
+ }
2288
+ /**
2289
+ * Register a browser push subscription with the server.
2290
+ *
2291
+ * Call this after the user grants notification permission and you
2292
+ * obtain a PushSubscription from `pushManager.subscribe()`.
2293
+ *
2294
+ * @param userId - External user ID
2295
+ * @param subscription - The PushSubscription object (or its JSON)
2296
+ * @returns Subscription ID from the server
2297
+ */
2298
+ async registerPush(userId, subscription) {
2299
+ const json = "toJSON" in subscription ? subscription.toJSON() : subscription;
2300
+ return this.http.post(
2301
+ "/notifications/push/subscribe",
2302
+ {
2303
+ user_id: userId,
2304
+ endpoint: json.endpoint,
2305
+ keys: json.keys,
2306
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
2307
+ }
2308
+ );
2309
+ }
2310
+ /**
2311
+ * Remove a push subscription from the server.
2312
+ *
2313
+ * Call this when the user logs out or revokes notification permission.
2314
+ *
2315
+ * @param userId - External user ID
2316
+ * @param endpoint - The push endpoint URL to remove
2317
+ */
2318
+ async unregisterPush(userId, endpoint) {
2319
+ return this.http.post(
2320
+ "/notifications/push/unsubscribe",
2321
+ { user_id: userId, endpoint }
2322
+ );
2323
+ }
2324
+ /**
2325
+ * High-level helper: request permission, subscribe to push, and register.
2326
+ *
2327
+ * Handles the full Web Push registration flow:
2328
+ * 1. Fetches the VAPID public key from the server
2329
+ * 2. Requests notification permission from the user
2330
+ * 3. Registers a Service Worker (if not already registered)
2331
+ * 4. Subscribes to push via the Push API
2332
+ * 5. Sends the subscription to the server
2333
+ *
2334
+ * @param userId - External user ID
2335
+ * @param serviceWorkerPath - Path to the service worker file (default: '/sw.js')
2336
+ * @returns The PushSubscription, or null if denied/unavailable
2337
+ *
2338
+ * @example
2339
+ * ```typescript
2340
+ * const sub = await client.notifications.requestPermissionAndSubscribe(
2341
+ * 'user-123',
2342
+ * '/proofchain-sw.js'
2343
+ * );
2344
+ * if (sub) {
2345
+ * console.log('Push notifications enabled!');
2346
+ * }
2347
+ * ```
2348
+ */
2349
+ async requestPermissionAndSubscribe(userId, serviceWorkerPath = "/sw.js") {
2350
+ if (typeof window === "undefined" || !("serviceWorker" in navigator) || !("PushManager" in window)) {
2351
+ console.warn("Web Push is not supported in this browser");
2352
+ return null;
2353
+ }
2354
+ const vapidKey = await this.getVapidKey();
2355
+ if (!vapidKey) {
2356
+ console.warn("Web Push not configured for this tenant");
2357
+ return null;
2358
+ }
2359
+ const permission = await Notification.requestPermission();
2360
+ if (permission !== "granted") {
2361
+ console.warn("Notification permission denied");
2362
+ return null;
2363
+ }
2364
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath);
2365
+ await navigator.serviceWorker.ready;
2366
+ const applicationServerKey = this._urlBase64ToUint8Array(vapidKey);
2367
+ const pushSubscription = await registration.pushManager.subscribe({
2368
+ userVisibleOnly: true,
2369
+ applicationServerKey: applicationServerKey.buffer
2370
+ });
2371
+ await this.registerPush(userId, pushSubscription);
2372
+ return pushSubscription;
2373
+ }
2374
+ /**
2375
+ * Generate a basic Service Worker script for handling push notifications.
2376
+ *
2377
+ * Returns JavaScript source code that can be saved as your sw.js file.
2378
+ * The generated worker handles `push` events (shows notification) and
2379
+ * `notificationclick` events (opens the app).
2380
+ *
2381
+ * @param defaultIcon - Optional default icon URL for notifications
2382
+ * @param defaultUrl - URL to open when notification is clicked (default: '/')
2383
+ * @returns Service Worker JavaScript source code as a string
2384
+ *
2385
+ * @example
2386
+ * ```typescript
2387
+ * // Save the output as /public/sw.js in your project
2388
+ * const swCode = client.notifications.generateServiceWorker({
2389
+ * defaultIcon: '/icon-192.png',
2390
+ * defaultUrl: '/',
2391
+ * });
2392
+ * ```
2393
+ */
2394
+ generateServiceWorker(options = {}) {
2395
+ const { defaultIcon = "/icon-192.png", defaultUrl = "/" } = options;
2396
+ return `// ProofChain Push Notification Service Worker
2397
+ // Auto-generated \u2014 place this file at the root of your public directory
2398
+
2399
+ self.addEventListener('push', function(event) {
2400
+ if (!event.data) return;
2401
+
2402
+ let payload;
2403
+ try {
2404
+ payload = event.data.json();
2405
+ } catch (e) {
2406
+ payload = { title: 'Notification', body: event.data.text() };
2407
+ }
2408
+
2409
+ const options = {
2410
+ body: payload.body || '',
2411
+ icon: payload.icon || '${defaultIcon}',
2412
+ badge: payload.badge || '${defaultIcon}',
2413
+ data: {
2414
+ url: payload.url || '${defaultUrl}',
2415
+ ...payload.data,
2416
+ },
2417
+ tag: payload.data?.event || 'proofchain',
2418
+ renotify: true,
2419
+ };
2420
+
2421
+ event.waitUntil(
2422
+ self.registration.showNotification(payload.title || 'Notification', options)
2423
+ );
2424
+ });
2425
+
2426
+ self.addEventListener('notificationclick', function(event) {
2427
+ event.notification.close();
2428
+ const url = event.notification.data?.url || '${defaultUrl}';
2429
+
2430
+ event.waitUntil(
2431
+ clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
2432
+ // Focus existing tab if open
2433
+ for (const client of clientList) {
2434
+ if (client.url === url && 'focus' in client) {
2435
+ return client.focus();
2436
+ }
2437
+ }
2438
+ // Otherwise open new tab
2439
+ return clients.openWindow(url);
2440
+ })
2441
+ );
2442
+ });
2443
+ `;
2444
+ }
2445
+ /** Convert a base64url-encoded VAPID key to a Uint8Array for the Push API. */
2446
+ _urlBase64ToUint8Array(base64String) {
2447
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
2448
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
2449
+ const rawData = atob(base64);
2450
+ const outputArray = new Uint8Array(rawData.length);
2451
+ for (let i = 0; i < rawData.length; ++i) {
2452
+ outputArray[i] = rawData.charCodeAt(i);
2453
+ }
2454
+ return outputArray;
2455
+ }
2456
+ };
2457
+
2458
+ // src/credentials.ts
2459
+ var CredentialsClient = class {
2460
+ constructor(http) {
2461
+ this.http = http;
2462
+ }
2463
+ // ---------------------------------------------------------------------------
2464
+ // Credential Types
2465
+ // ---------------------------------------------------------------------------
2466
+ /**
2467
+ * Create a new credential type
2468
+ */
2469
+ async createType(request) {
2470
+ return this.http.post("/credentials/types", request);
2471
+ }
2472
+ /**
2473
+ * List credential types
2474
+ */
2475
+ async listTypes(options) {
2476
+ return this.http.get("/credentials/types", {
2477
+ status: options?.status,
2478
+ category: options?.category
2479
+ });
2480
+ }
2481
+ /**
2482
+ * Get a specific credential type
2483
+ */
2484
+ async getType(typeId) {
2485
+ return this.http.get(`/credentials/types/${typeId}`);
2486
+ }
2487
+ /**
2488
+ * Update a credential type
2489
+ */
2490
+ async updateType(typeId, request) {
2491
+ return this.http.put(`/credentials/types/${typeId}`, request);
2492
+ }
2493
+ /**
2494
+ * Activate a credential type
2495
+ */
2496
+ async activateType(typeId) {
2497
+ return this.http.post(`/credentials/types/${typeId}/activate`, {});
2498
+ }
2499
+ /**
2500
+ * Archive a credential type
2501
+ */
2502
+ async archiveType(typeId) {
2503
+ return this.http.post(`/credentials/types/${typeId}/archive`, {});
2504
+ }
2505
+ // ---------------------------------------------------------------------------
2506
+ // Credential Issuance
2507
+ // ---------------------------------------------------------------------------
2508
+ /**
2509
+ * Issue a credential to a user
2510
+ */
2511
+ async issue(request) {
2512
+ return this.http.post("/credentials/issue", request);
2513
+ }
2514
+ /**
2515
+ * List issued credentials
2516
+ */
2517
+ async listIssued(options) {
2518
+ return this.http.get("/credentials/issued", {
2519
+ user_id: options?.user_id,
2520
+ credential_type_id: options?.credential_type_id,
2521
+ status: options?.status,
2522
+ limit: options?.limit,
2523
+ offset: options?.offset
2524
+ });
2525
+ }
2526
+ /**
2527
+ * Revoke an issued credential
2528
+ */
2529
+ async revoke(credentialId, reason) {
2530
+ const params = reason ? `?reason=${encodeURIComponent(reason)}` : "";
2531
+ return this.http.post(`/credentials/issued/${credentialId}/revoke${params}`, {});
2532
+ }
2533
+ /**
2534
+ * Suspend an issued credential
2535
+ */
2536
+ async suspend(credentialId, reason) {
2537
+ const params = reason ? `?reason=${encodeURIComponent(reason)}` : "";
2538
+ return this.http.post(`/credentials/issued/${credentialId}/suspend${params}`, {});
2539
+ }
2540
+ /**
2541
+ * Reinstate a suspended credential
2542
+ */
2543
+ async reinstate(credentialId) {
2544
+ return this.http.post(`/credentials/issued/${credentialId}/reinstate`, {});
2545
+ }
2546
+ // ---------------------------------------------------------------------------
2547
+ // User Management
2548
+ // ---------------------------------------------------------------------------
2549
+ /**
2550
+ * Opt a user in to identity credentials (tenant admin)
2551
+ */
2552
+ async optInUser(userExternalId) {
2553
+ return this.http.post(`/credentials/opt-in/${userExternalId}`, {});
2554
+ }
2555
+ /**
2556
+ * Opt a user out of identity credentials (tenant admin)
2557
+ */
2558
+ async optOutUser(userExternalId) {
2559
+ return this.http.post(`/credentials/opt-out/${userExternalId}`, {});
2560
+ }
2561
+ /**
2562
+ * Get all credentials for a user
2563
+ */
2564
+ async getUserCredentials(userExternalId) {
2565
+ return this.http.get(`/credentials/user/${userExternalId}`);
2566
+ }
2567
+ // ---------------------------------------------------------------------------
2568
+ // Public Verification (no auth needed)
2569
+ // ---------------------------------------------------------------------------
2570
+ /**
2571
+ * Verify a credential by its verification code.
2572
+ * This is a public endpoint — no authentication required.
2573
+ */
2574
+ async verify(verificationCode) {
2575
+ return this.http.get(`/credentials/verify/${verificationCode}`);
2576
+ }
2190
2577
  };
2191
2578
 
2192
2579
  // src/client.ts
@@ -2487,6 +2874,7 @@ var ProofChain = class _ProofChain {
2487
2874
  this.cohorts = new CohortLeaderboardClient(this.http);
2488
2875
  this.fanpassLeaderboard = new FanpassLeaderboardClient(this.http);
2489
2876
  this.notifications = new NotificationsClient(this.http);
2877
+ this.credentials = new CredentialsClient(this.http);
2490
2878
  }
2491
2879
  /**
2492
2880
  * Create a client for end-user JWT authentication (PWA/frontend use).
@@ -2820,6 +3208,7 @@ export {
2820
3208
  CertificatesResource,
2821
3209
  ChannelsResource,
2822
3210
  CohortLeaderboardClient,
3211
+ CredentialsClient,
2823
3212
  DataViewsClient,
2824
3213
  DocumentsResource,
2825
3214
  EndUserIngestionClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofchain/sdk",
3
- "version": "2.17.0",
3
+ "version": "2.20.0",
4
4
  "description": "Official JavaScript/TypeScript SDK for ProofChain - blockchain-anchored document attestation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",