@proofchain/sdk 2.16.0 → 2.19.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,
@@ -34,6 +35,7 @@ __export(index_exports, {
34
35
  IngestionClient: () => IngestionClient,
35
36
  NetworkError: () => NetworkError,
36
37
  NotFoundError: () => NotFoundError,
38
+ NotificationsClient: () => NotificationsClient,
37
39
  PassportClient: () => PassportClient,
38
40
  ProofChain: () => ProofChain,
39
41
  ProofChainError: () => ProofChainError,
@@ -1614,6 +1616,71 @@ var RewardsClient = class {
1614
1616
  async listAssets(definitionId) {
1615
1617
  return this.http.get(`/rewards/definitions/${definitionId}/assets`);
1616
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
+ }
1617
1684
  };
1618
1685
 
1619
1686
  // src/quests.ts
@@ -2149,6 +2216,408 @@ var FanpassLeaderboardClient = class {
2149
2216
  }
2150
2217
  };
2151
2218
 
2219
+ // src/notifications.ts
2220
+ var NotificationsClient = class {
2221
+ constructor(http) {
2222
+ this.http = http;
2223
+ this.baseUrl = http.baseUrl || "https://api.proofchain.co.za";
2224
+ this.authHeaders = () => {
2225
+ const headers = {};
2226
+ if (http.apiKey) {
2227
+ headers["X-API-Key"] = http.apiKey;
2228
+ }
2229
+ if (http.userToken) {
2230
+ headers["Authorization"] = `Bearer ${http.userToken}`;
2231
+ if (http.tenantId) {
2232
+ headers["X-Tenant-ID"] = http.tenantId;
2233
+ }
2234
+ }
2235
+ return headers;
2236
+ };
2237
+ }
2238
+ /**
2239
+ * Subscribe to real-time notifications for a user.
2240
+ *
2241
+ * Opens a Server-Sent Events connection and calls the callback
2242
+ * for each notification event (quest progress, rewards, etc.).
2243
+ *
2244
+ * Returns an unsubscribe function to close the connection.
2245
+ *
2246
+ * **Browser usage:** Works out of the box with EventSource.
2247
+ * **Node.js usage:** Requires the `eventsource` package.
2248
+ *
2249
+ * @param userId - The external user ID to subscribe for
2250
+ * @param callback - Function called for each notification event
2251
+ * @param options - Connection options (reconnect, callbacks)
2252
+ * @returns Unsubscribe function to close the connection
2253
+ */
2254
+ subscribe(userId, callback, options = {}) {
2255
+ const {
2256
+ onConnect,
2257
+ onDisconnect,
2258
+ onError,
2259
+ autoReconnect = true,
2260
+ reconnectDelay = 3e3
2261
+ } = options;
2262
+ let eventSource = null;
2263
+ let closed = false;
2264
+ let reconnectTimer = null;
2265
+ const connect = () => {
2266
+ if (closed) return;
2267
+ const headers = this.authHeaders();
2268
+ const params = new URLSearchParams({ user_id: userId });
2269
+ if (headers["X-API-Key"]) {
2270
+ params.append("api_key", headers["X-API-Key"]);
2271
+ }
2272
+ if (headers["Authorization"]) {
2273
+ params.append("token", headers["Authorization"].replace("Bearer ", ""));
2274
+ }
2275
+ if (headers["X-Tenant-ID"]) {
2276
+ params.append("tenant_id", headers["X-Tenant-ID"]);
2277
+ }
2278
+ const url = `${this.baseUrl}/notifications/stream?${params.toString()}`;
2279
+ const ES = typeof EventSource !== "undefined" ? EventSource : (() => {
2280
+ throw new Error('EventSource not available. Install the "eventsource" package for Node.js.');
2281
+ })();
2282
+ eventSource = new ES(url);
2283
+ eventSource.onmessage = (e) => {
2284
+ try {
2285
+ const event = JSON.parse(e.data);
2286
+ if (event.event === "connected") {
2287
+ onConnect?.();
2288
+ } else {
2289
+ callback(event);
2290
+ }
2291
+ } catch (err) {
2292
+ onError?.(new Error(`Failed to parse notification: ${e.data}`));
2293
+ }
2294
+ };
2295
+ eventSource.onerror = () => {
2296
+ if (closed) return;
2297
+ onDisconnect?.();
2298
+ if (eventSource?.readyState === 2 && autoReconnect) {
2299
+ reconnectTimer = setTimeout(connect, reconnectDelay);
2300
+ }
2301
+ };
2302
+ };
2303
+ connect();
2304
+ return () => {
2305
+ closed = true;
2306
+ if (reconnectTimer) clearTimeout(reconnectTimer);
2307
+ if (eventSource) {
2308
+ eventSource.close();
2309
+ eventSource = null;
2310
+ }
2311
+ };
2312
+ }
2313
+ // ===========================================================================
2314
+ // WEB PUSH (OS-level notifications, even when browser is closed)
2315
+ // ===========================================================================
2316
+ /**
2317
+ * Get the tenant's VAPID public key for Web Push.
2318
+ *
2319
+ * This key is needed to call `pushManager.subscribe()` in the browser.
2320
+ * Returns null if Web Push is not configured for this tenant.
2321
+ */
2322
+ async getVapidKey() {
2323
+ try {
2324
+ const res = await this.http.get("/notifications/push/vapid-key");
2325
+ return res.vapid_public_key;
2326
+ } catch {
2327
+ return null;
2328
+ }
2329
+ }
2330
+ /**
2331
+ * Register a browser push subscription with the server.
2332
+ *
2333
+ * Call this after the user grants notification permission and you
2334
+ * obtain a PushSubscription from `pushManager.subscribe()`.
2335
+ *
2336
+ * @param userId - External user ID
2337
+ * @param subscription - The PushSubscription object (or its JSON)
2338
+ * @returns Subscription ID from the server
2339
+ */
2340
+ async registerPush(userId, subscription) {
2341
+ const json = "toJSON" in subscription ? subscription.toJSON() : subscription;
2342
+ return this.http.post(
2343
+ "/notifications/push/subscribe",
2344
+ {
2345
+ user_id: userId,
2346
+ endpoint: json.endpoint,
2347
+ keys: json.keys,
2348
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
2349
+ }
2350
+ );
2351
+ }
2352
+ /**
2353
+ * Remove a push subscription from the server.
2354
+ *
2355
+ * Call this when the user logs out or revokes notification permission.
2356
+ *
2357
+ * @param userId - External user ID
2358
+ * @param endpoint - The push endpoint URL to remove
2359
+ */
2360
+ async unregisterPush(userId, endpoint) {
2361
+ return this.http.post(
2362
+ "/notifications/push/unsubscribe",
2363
+ { user_id: userId, endpoint }
2364
+ );
2365
+ }
2366
+ /**
2367
+ * High-level helper: request permission, subscribe to push, and register.
2368
+ *
2369
+ * Handles the full Web Push registration flow:
2370
+ * 1. Fetches the VAPID public key from the server
2371
+ * 2. Requests notification permission from the user
2372
+ * 3. Registers a Service Worker (if not already registered)
2373
+ * 4. Subscribes to push via the Push API
2374
+ * 5. Sends the subscription to the server
2375
+ *
2376
+ * @param userId - External user ID
2377
+ * @param serviceWorkerPath - Path to the service worker file (default: '/sw.js')
2378
+ * @returns The PushSubscription, or null if denied/unavailable
2379
+ *
2380
+ * @example
2381
+ * ```typescript
2382
+ * const sub = await client.notifications.requestPermissionAndSubscribe(
2383
+ * 'user-123',
2384
+ * '/proofchain-sw.js'
2385
+ * );
2386
+ * if (sub) {
2387
+ * console.log('Push notifications enabled!');
2388
+ * }
2389
+ * ```
2390
+ */
2391
+ async requestPermissionAndSubscribe(userId, serviceWorkerPath = "/sw.js") {
2392
+ if (typeof window === "undefined" || !("serviceWorker" in navigator) || !("PushManager" in window)) {
2393
+ console.warn("Web Push is not supported in this browser");
2394
+ return null;
2395
+ }
2396
+ const vapidKey = await this.getVapidKey();
2397
+ if (!vapidKey) {
2398
+ console.warn("Web Push not configured for this tenant");
2399
+ return null;
2400
+ }
2401
+ const permission = await Notification.requestPermission();
2402
+ if (permission !== "granted") {
2403
+ console.warn("Notification permission denied");
2404
+ return null;
2405
+ }
2406
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath);
2407
+ await navigator.serviceWorker.ready;
2408
+ const applicationServerKey = this._urlBase64ToUint8Array(vapidKey);
2409
+ const pushSubscription = await registration.pushManager.subscribe({
2410
+ userVisibleOnly: true,
2411
+ applicationServerKey: applicationServerKey.buffer
2412
+ });
2413
+ await this.registerPush(userId, pushSubscription);
2414
+ return pushSubscription;
2415
+ }
2416
+ /**
2417
+ * Generate a basic Service Worker script for handling push notifications.
2418
+ *
2419
+ * Returns JavaScript source code that can be saved as your sw.js file.
2420
+ * The generated worker handles `push` events (shows notification) and
2421
+ * `notificationclick` events (opens the app).
2422
+ *
2423
+ * @param defaultIcon - Optional default icon URL for notifications
2424
+ * @param defaultUrl - URL to open when notification is clicked (default: '/')
2425
+ * @returns Service Worker JavaScript source code as a string
2426
+ *
2427
+ * @example
2428
+ * ```typescript
2429
+ * // Save the output as /public/sw.js in your project
2430
+ * const swCode = client.notifications.generateServiceWorker({
2431
+ * defaultIcon: '/icon-192.png',
2432
+ * defaultUrl: '/',
2433
+ * });
2434
+ * ```
2435
+ */
2436
+ generateServiceWorker(options = {}) {
2437
+ const { defaultIcon = "/icon-192.png", defaultUrl = "/" } = options;
2438
+ return `// ProofChain Push Notification Service Worker
2439
+ // Auto-generated \u2014 place this file at the root of your public directory
2440
+
2441
+ self.addEventListener('push', function(event) {
2442
+ if (!event.data) return;
2443
+
2444
+ let payload;
2445
+ try {
2446
+ payload = event.data.json();
2447
+ } catch (e) {
2448
+ payload = { title: 'Notification', body: event.data.text() };
2449
+ }
2450
+
2451
+ const options = {
2452
+ body: payload.body || '',
2453
+ icon: payload.icon || '${defaultIcon}',
2454
+ badge: payload.badge || '${defaultIcon}',
2455
+ data: {
2456
+ url: payload.url || '${defaultUrl}',
2457
+ ...payload.data,
2458
+ },
2459
+ tag: payload.data?.event || 'proofchain',
2460
+ renotify: true,
2461
+ };
2462
+
2463
+ event.waitUntil(
2464
+ self.registration.showNotification(payload.title || 'Notification', options)
2465
+ );
2466
+ });
2467
+
2468
+ self.addEventListener('notificationclick', function(event) {
2469
+ event.notification.close();
2470
+ const url = event.notification.data?.url || '${defaultUrl}';
2471
+
2472
+ event.waitUntil(
2473
+ clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function(clientList) {
2474
+ // Focus existing tab if open
2475
+ for (const client of clientList) {
2476
+ if (client.url === url && 'focus' in client) {
2477
+ return client.focus();
2478
+ }
2479
+ }
2480
+ // Otherwise open new tab
2481
+ return clients.openWindow(url);
2482
+ })
2483
+ );
2484
+ });
2485
+ `;
2486
+ }
2487
+ /** Convert a base64url-encoded VAPID key to a Uint8Array for the Push API. */
2488
+ _urlBase64ToUint8Array(base64String) {
2489
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
2490
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
2491
+ const rawData = atob(base64);
2492
+ const outputArray = new Uint8Array(rawData.length);
2493
+ for (let i = 0; i < rawData.length; ++i) {
2494
+ outputArray[i] = rawData.charCodeAt(i);
2495
+ }
2496
+ return outputArray;
2497
+ }
2498
+ };
2499
+
2500
+ // src/credentials.ts
2501
+ var CredentialsClient = class {
2502
+ constructor(http) {
2503
+ this.http = http;
2504
+ }
2505
+ // ---------------------------------------------------------------------------
2506
+ // Credential Types
2507
+ // ---------------------------------------------------------------------------
2508
+ /**
2509
+ * Create a new credential type
2510
+ */
2511
+ async createType(request) {
2512
+ return this.http.post("/credentials/types", request);
2513
+ }
2514
+ /**
2515
+ * List credential types
2516
+ */
2517
+ async listTypes(options) {
2518
+ return this.http.get("/credentials/types", {
2519
+ status: options?.status,
2520
+ category: options?.category
2521
+ });
2522
+ }
2523
+ /**
2524
+ * Get a specific credential type
2525
+ */
2526
+ async getType(typeId) {
2527
+ return this.http.get(`/credentials/types/${typeId}`);
2528
+ }
2529
+ /**
2530
+ * Update a credential type
2531
+ */
2532
+ async updateType(typeId, request) {
2533
+ return this.http.put(`/credentials/types/${typeId}`, request);
2534
+ }
2535
+ /**
2536
+ * Activate a credential type
2537
+ */
2538
+ async activateType(typeId) {
2539
+ return this.http.post(`/credentials/types/${typeId}/activate`, {});
2540
+ }
2541
+ /**
2542
+ * Archive a credential type
2543
+ */
2544
+ async archiveType(typeId) {
2545
+ return this.http.post(`/credentials/types/${typeId}/archive`, {});
2546
+ }
2547
+ // ---------------------------------------------------------------------------
2548
+ // Credential Issuance
2549
+ // ---------------------------------------------------------------------------
2550
+ /**
2551
+ * Issue a credential to a user
2552
+ */
2553
+ async issue(request) {
2554
+ return this.http.post("/credentials/issue", request);
2555
+ }
2556
+ /**
2557
+ * List issued credentials
2558
+ */
2559
+ async listIssued(options) {
2560
+ return this.http.get("/credentials/issued", {
2561
+ user_id: options?.user_id,
2562
+ credential_type_id: options?.credential_type_id,
2563
+ status: options?.status,
2564
+ limit: options?.limit,
2565
+ offset: options?.offset
2566
+ });
2567
+ }
2568
+ /**
2569
+ * Revoke an issued credential
2570
+ */
2571
+ async revoke(credentialId, reason) {
2572
+ const params = reason ? `?reason=${encodeURIComponent(reason)}` : "";
2573
+ return this.http.post(`/credentials/issued/${credentialId}/revoke${params}`, {});
2574
+ }
2575
+ /**
2576
+ * Suspend an issued credential
2577
+ */
2578
+ async suspend(credentialId, reason) {
2579
+ const params = reason ? `?reason=${encodeURIComponent(reason)}` : "";
2580
+ return this.http.post(`/credentials/issued/${credentialId}/suspend${params}`, {});
2581
+ }
2582
+ /**
2583
+ * Reinstate a suspended credential
2584
+ */
2585
+ async reinstate(credentialId) {
2586
+ return this.http.post(`/credentials/issued/${credentialId}/reinstate`, {});
2587
+ }
2588
+ // ---------------------------------------------------------------------------
2589
+ // User Management
2590
+ // ---------------------------------------------------------------------------
2591
+ /**
2592
+ * Opt a user in to identity credentials (tenant admin)
2593
+ */
2594
+ async optInUser(userExternalId) {
2595
+ return this.http.post(`/credentials/opt-in/${userExternalId}`, {});
2596
+ }
2597
+ /**
2598
+ * Opt a user out of identity credentials (tenant admin)
2599
+ */
2600
+ async optOutUser(userExternalId) {
2601
+ return this.http.post(`/credentials/opt-out/${userExternalId}`, {});
2602
+ }
2603
+ /**
2604
+ * Get all credentials for a user
2605
+ */
2606
+ async getUserCredentials(userExternalId) {
2607
+ return this.http.get(`/credentials/user/${userExternalId}`);
2608
+ }
2609
+ // ---------------------------------------------------------------------------
2610
+ // Public Verification (no auth needed)
2611
+ // ---------------------------------------------------------------------------
2612
+ /**
2613
+ * Verify a credential by its verification code.
2614
+ * This is a public endpoint — no authentication required.
2615
+ */
2616
+ async verify(verificationCode) {
2617
+ return this.http.get(`/credentials/verify/${verificationCode}`);
2618
+ }
2619
+ };
2620
+
2152
2621
  // src/client.ts
2153
2622
  var DocumentsResource = class {
2154
2623
  constructor(http) {
@@ -2446,6 +2915,8 @@ var ProofChain = class _ProofChain {
2446
2915
  this.dataViews = new DataViewsClient(this.http);
2447
2916
  this.cohorts = new CohortLeaderboardClient(this.http);
2448
2917
  this.fanpassLeaderboard = new FanpassLeaderboardClient(this.http);
2918
+ this.notifications = new NotificationsClient(this.http);
2919
+ this.credentials = new CredentialsClient(this.http);
2449
2920
  }
2450
2921
  /**
2451
2922
  * Create a client for end-user JWT authentication (PWA/frontend use).
@@ -2780,6 +3251,7 @@ var EndUserIngestionClient = class {
2780
3251
  CertificatesResource,
2781
3252
  ChannelsResource,
2782
3253
  CohortLeaderboardClient,
3254
+ CredentialsClient,
2783
3255
  DataViewsClient,
2784
3256
  DocumentsResource,
2785
3257
  EndUserIngestionClient,
@@ -2789,6 +3261,7 @@ var EndUserIngestionClient = class {
2789
3261
  IngestionClient,
2790
3262
  NetworkError,
2791
3263
  NotFoundError,
3264
+ NotificationsClient,
2792
3265
  PassportClient,
2793
3266
  ProofChain,
2794
3267
  ProofChainError,