@nibgate/sdk 0.2.0 → 0.2.1

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/SKILL.md CHANGED
@@ -212,6 +212,21 @@ app.use(admin.router(express))
212
212
  // app.post('/admin/nibgate/resources/:id', admin.handleUpdate)
213
213
  ```
214
214
 
215
+ ### Postgres store
216
+
217
+ For production (Render, Railway, Fly), use the Postgres store instead of the file store (file changes get wiped on redeploy):
218
+
219
+ ```ts
220
+ import pkg from 'pg'
221
+ const { Pool } = pkg
222
+
223
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL })
224
+ const store = createPostgresStore(pool, { table: 'nibgate_settings' })
225
+ const admin = createAdminApi({ store })
226
+ ```
227
+
228
+ The Postgres store auto-creates the table. It uses JSONB for settings and `ON CONFLICT` upserts. Works with any `pg` Pool-like interface.
229
+
215
230
  ### Protect the admin route
216
231
 
217
232
  Pass an `authorize` function to `createAdminApi`:
@@ -308,7 +323,18 @@ NIBGATE_BUYER_PRIVATE_KEY=0x... # Only for local testing
308
323
 
309
324
  ### For plain HTML/JS sites without a bundler
310
325
 
311
- The SDK relies on bare module imports (`viem`, `@x402/core`). These won't work in the browser without a bundler. Compile a browser-ready bundle:
326
+ For plain HTML/JS sites without a bundler, use the pre-built CDN bundle:
327
+
328
+ ```html
329
+ <script src="https://unpkg.com/@nibgate/sdk@0.2.0/dist/nibgate.min.js"></script>
330
+ <script>
331
+ const { nibgate, gate, mountRatingUI, createEvmGatewayUnlock, createOnchainRating } = Nibgate
332
+ </script>
333
+ ```
334
+
335
+ The bundle includes all dependencies (viem, x402) compiled into a single IIFE script. All browser exports are on the `Nibgate` global. No esbuild step needed.
336
+
337
+ If you prefer to build your own bundle:
312
338
 
313
339
  ```bash
314
340
  esbuild --bundle --format=iife --global-name=Nibgate \
@@ -316,15 +342,6 @@ esbuild --bundle --format=iife --global-name=Nibgate \
316
342
  node_modules/@nibgate/sdk/src/browser/index.js
317
343
  ```
318
344
 
319
- Then load it:
320
-
321
- ```html
322
- <script src="/nibgate-bundle.js"></script>
323
- <script>
324
- const { nibgate, gate } = Nibgate
325
- </script>
326
- ```
327
-
328
345
  ### Smart Contract Wallets
329
346
 
330
347
  Some smart contract wallets (Gnosis Safe, Argent, etc.) may not support `eth_signTypedData_v4` required by the Gateway payment signing. Test with an EOA (MetaMask, Rabby, Coinbase Wallet) first. If your users primarily use smart contract wallets, use the server-side pay route (`payAndUnlockResponse`) with `NIBGATE_BUYER_PRIVATE_KEY` instead.
@@ -412,7 +429,48 @@ Only expose values with `NEXT_PUBLIC_` or client-side env names when they are in
412
429
 
413
430
  ---
414
431
 
415
- ## 13. Never Do These
432
+ ## 13. Webhooks (Server-to-Server Notifications)
433
+
434
+ Receive notifications when payments, unlocks, or ratings happen — without polling.
435
+
436
+ ### Setup
437
+
438
+ ```ts
439
+ import { createWebhookManager, createWebhookApi } from '@nibgate/sdk/server'
440
+ import express from 'express'
441
+
442
+ const manager = createWebhookManager({
443
+ webhookUrl: 'https://my-server.com/nibgate-webhook',
444
+ webhookSecret: process.env.NIBGATE_WEBHOOK_SECRET
445
+ })
446
+
447
+ // Mount admin API for subscribing webhooks
448
+ const webhookApi = createWebhookApi(manager, { adminKey: process.env.ADMIN_KEY })
449
+ app.use(webhookApi.router(express))
450
+
451
+ // Emit events from your code
452
+ await manager.emit('payment.completed', { contentId: 'post-1', amount: '0.01', payer: '0x...' })
453
+ await manager.emit('content.unlocked', { contentId: 'post-1', actor: 'human' })
454
+ await manager.emit('content.rated', { contentId: 'post-1', rating: 4, rater: '0x...' })
455
+ ```
456
+
457
+ ### Webhook payload format
458
+
459
+ ```json
460
+ {
461
+ "event": "payment.completed",
462
+ "timestamp": "2026-07-09T12:00:00.000Z",
463
+ "data": { "contentId": "post-1", "amount": "0.01" }
464
+ }
465
+ ```
466
+
467
+ The `x-nibgate-webhook-signature` header contains an HMAC-SHA256 signature of the body using your webhook secret. Verify it on the receiving end to confirm the webhook is authentic.
468
+
469
+ Events emitted automatically by `createNibgateServer`: `payment_completed`, `unlock_completed`.
470
+
471
+ ---
472
+
473
+ ## 14. Never Do These
416
474
 
417
475
  - Do not hide paid HTML with CSS or client state after rendering it publicly
418
476
  - Do not fake payment IDs, receipts, unlocks, or successful access
@@ -427,6 +485,19 @@ Only expose values with `NEXT_PUBLIC_` or client-side env names when they are in
427
485
 
428
486
  - **Next.js**: Use route handlers for `/nibgate.json` and `/api/nibgate/access`. Protect content before rendering paid payloads. Use `server-only` for private modules.
429
487
  - **Express/NestJS**: Use middleware, controllers, or guards around protected routes. Mount the admin router at `/admin/nibgate`.
488
+
489
+ For a one-line payment verification middleware:
490
+
491
+ ```ts
492
+ import { verifyPayment } from '@nibgate/sdk/server'
493
+
494
+ app.get('/api/content/:id', verifyPayment({ resource: { id: 'my-post', price: '0.01' } }), (req, res) => {
495
+ // req.nibgate.verified is true
496
+ res.json({ content: 'Protected content here' })
497
+ })
498
+ ```
499
+
500
+ The middleware checks `x-nibgate-payment-proof` (existing unlock) and `payment-signature` (new Gateway payment). Returns 402 with a challenge if neither is present.
430
501
  - **Astro/SvelteKit/Remix**: Use SSR/server routes or endpoints. Plain static builds need a protected API or signed URL for private payloads.
431
502
  - **CMS apps**: Save Nibgate resource settings beside each content record. Use `settingsToAccessPolicy()` and `settingsToUnlockPolicy()` to convert stored settings to resource shapes. Mount the admin panel for easy management.
432
503
  - **Static/vanilla HTML/JS**: Compile the SDK into a bundle with esbuild (see section 8). Use `gate()` and `nibgate` from the global `Nibgate` namespace.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nibgate/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Framework-agnostic browser and server package for creator-owned gated content, unlock events, and receipts.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -218,10 +218,12 @@ export function protect(resource, handler, options = {}) {
218
218
 
219
219
  export function verifyPayment(options = {}) {
220
220
  return async function verifyPaymentMiddleware(req, res, next) {
221
- const proofHeader = req.headers?.['x-nibgate-payment-proof'] || req.get?.('x-nibgate-payment-proof') || '';
221
+ const contentId = req.params?.id || req.body?.resourceId || '';
222
+
223
+ const proofHeader = req.headers?.['x-nibgate-payment-proof'] || '';
222
224
  if (proofHeader) {
223
225
  const nibgate = createNibgateServer(options);
224
- const payload = nibgate.verifyUnlockToken(proofHeader, { id: req.params?.id || req.body?.resourceId || '' });
226
+ const payload = nibgate.verifyUnlockToken(proofHeader, { id: contentId });
225
227
  if (payload) {
226
228
  req.nibgate = { unlock: payload, verified: true };
227
229
  return next();
@@ -229,11 +231,16 @@ export function verifyPayment(options = {}) {
229
231
  }
230
232
 
231
233
  if ((options.paymentMode || process.env.NIBGATE_PAYMENT_MODE) === 'circle-gateway') {
232
- const sigHeader = req.headers?.['payment-signature'] || req.get?.('payment-signature') || '';
234
+ const sigHeader = req.headers?.['payment-signature'] || '';
233
235
  if (sigHeader) {
234
- const resource = options.resource || { id: req.params?.id || '', price: req.body?.price || '0' };
236
+ const resource = options.resource || { id: contentId, price: req.body?.price || '0' };
237
+ const gatewayHeaders = Object.assign(Object.create(null), req.headers || {});
238
+ gatewayHeaders.forEach = function (cb) {
239
+ var self = this;
240
+ Object.keys(self).forEach(function (k) { if (k !== 'forEach') cb(self[k], k); });
241
+ };
235
242
  const gateway = await runCircleGatewayRequirement(
236
- { headers: new Map(Object.entries(req.headers || {})), method: req.method, url: req.originalUrl || req.url },
243
+ { headers: gatewayHeaders, method: req.method || 'GET', url: req.originalUrl || req.url || '/' },
237
244
  resource,
238
245
  options
239
246
  );
@@ -245,9 +252,7 @@ export function verifyPayment(options = {}) {
245
252
  }
246
253
  }
247
254
 
248
- return res.status(402).json({
249
- error: 'Payment required',
250
- challenge: createPaymentChallenge(options.resource || { id: req.params?.id || '', price: req.body?.price || '0' }, options)
251
- });
255
+ const errBody = createPaymentChallenge(options.resource || { id: contentId, price: req.body?.price || '0' }, options);
256
+ return res.status(402).json(errBody);
252
257
  };
253
258
  }