@nibgate/sdk 0.1.9 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nibgate/sdk",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
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,
@@ -28,20 +28,26 @@
28
28
  "import": "./src/testing.js",
29
29
  "default": "./src/testing.js"
30
30
  },
31
- "./package.json": "./package.json"
31
+ "./package.json": "./package.json",
32
+ "./dist/*": "./dist/*"
32
33
  },
33
34
  "scripts": {
34
35
  "pack:check": "npm pack --dry-run",
35
- "prepublishOnly": "npm run pack:check"
36
+ "build": "node scripts/bundle.mjs",
37
+ "prepublishOnly": "npm run build && npm run pack:check"
36
38
  },
37
39
  "publishConfig": {
38
40
  "access": "public"
39
41
  },
40
42
  "files": [
41
43
  "src",
44
+ "dist",
42
45
  "README.md",
43
46
  "SKILL.md"
44
47
  ],
48
+ "devDependencies": {
49
+ "esbuild": "^0.25.12"
50
+ },
45
51
  "keywords": [
46
52
  "nibgate",
47
53
  "creator",
@@ -215,3 +215,39 @@ export function createNibgateServer(options = {}) {
215
215
  export function protect(resource, handler, options = {}) {
216
216
  return createNibgateServer(options).protect(resource, handler);
217
217
  }
218
+
219
+ export function verifyPayment(options = {}) {
220
+ return async function verifyPaymentMiddleware(req, res, next) {
221
+ const proofHeader = req.headers?.['x-nibgate-payment-proof'] || req.get?.('x-nibgate-payment-proof') || '';
222
+ if (proofHeader) {
223
+ const nibgate = createNibgateServer(options);
224
+ const payload = nibgate.verifyUnlockToken(proofHeader, { id: req.params?.id || req.body?.resourceId || '' });
225
+ if (payload) {
226
+ req.nibgate = { unlock: payload, verified: true };
227
+ return next();
228
+ }
229
+ }
230
+
231
+ if ((options.paymentMode || process.env.NIBGATE_PAYMENT_MODE) === 'circle-gateway') {
232
+ const sigHeader = req.headers?.['payment-signature'] || req.get?.('payment-signature') || '';
233
+ if (sigHeader) {
234
+ const resource = options.resource || { id: req.params?.id || '', price: req.body?.price || '0' };
235
+ const gateway = await runCircleGatewayRequirement(
236
+ { headers: new Map(Object.entries(req.headers || {})), method: req.method, url: req.originalUrl || req.url },
237
+ resource,
238
+ options
239
+ );
240
+ if (!gateway.handled) {
241
+ req.nibgate = { payment: gateway.payment, verified: true };
242
+ return next();
243
+ }
244
+ return res.status(402).json({ error: 'Payment verification failed', detail: 'The payment signature could not be verified.' });
245
+ }
246
+ }
247
+
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
+ });
252
+ };
253
+ }
@@ -58,3 +58,50 @@ export function createMemoryStore() {
58
58
  remove(id) { delete store[id]; return true; }
59
59
  };
60
60
  }
61
+
62
+ export function createPostgresStore(pool, options = {}) {
63
+ const table = options.table || 'nibgate_settings';
64
+ const idColumn = options.idColumn || 'id';
65
+
66
+ async function ensureTable() {
67
+ await pool.query(`
68
+ CREATE TABLE IF NOT EXISTS "${table}" (
69
+ "${idColumn}" TEXT PRIMARY KEY,
70
+ "settings" JSONB NOT NULL DEFAULT '{}',
71
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
72
+ )
73
+ `);
74
+ }
75
+
76
+ async function list() {
77
+ const { rows } = await pool.query(`SELECT "${idColumn}" as id, settings FROM "${table}" ORDER BY "updatedAt" DESC`);
78
+ return rows.map((row) => ({ id: row.id, ...row.settings }));
79
+ }
80
+
81
+ async function get(id) {
82
+ const { rows } = await pool.query(`SELECT settings FROM "${table}" WHERE "${idColumn}" = $1`, [id]);
83
+ if (!rows.length) return null;
84
+ return { id, ...rows[0].settings };
85
+ }
86
+
87
+ async function set(id, settings) {
88
+ const now = new Date().toISOString();
89
+ const merged = JSON.stringify({ ...settings, updatedAt: now });
90
+ await pool.query(`
91
+ INSERT INTO "${table}" ("${idColumn}", "settings", "updatedAt")
92
+ VALUES ($1, $2::jsonb, $3)
93
+ ON CONFLICT ("${idColumn}")
94
+ DO UPDATE SET "settings" = ("${table}"."settings" || $2::jsonb), "updatedAt" = $3
95
+ `, [id, merged, now]);
96
+ return { id, ...settings, updatedAt: now };
97
+ }
98
+
99
+ async function remove(id) {
100
+ await pool.query(`DELETE FROM "${table}" WHERE "${idColumn}" = $1`, [id]);
101
+ return true;
102
+ }
103
+
104
+ const store = { list, get, set, remove };
105
+ if (options.autoInit !== false) ensureTable().catch(() => {});
106
+ return store;
107
+ }
@@ -6,7 +6,7 @@ export { createManifest, manifestResponse } from './manifest.js';
6
6
  export { createUnlockToken, verifyUnlockToken } from './proof.js';
7
7
  export { emitHubEvent } from './hub.js';
8
8
  export { payWithGateway, createGatewayBuyer, getGatewayBalances, depositToGateway, withdrawFromGateway } from './gateway.js';
9
- export { createNibgateServer, protect } from './access.js';
9
+ export { createNibgateServer, protect, verifyPayment } from './access.js';
10
10
  export { circleGatewayOptions, createCircleGatewayServer } from './presets.js';
11
11
  export { normalizeServerResource as normalizeResource, normalizeAccessPolicy, normalizeUnlockPolicy, validateResourceMetadata, UNLOCK_MODES } from '../core/resource.js';
12
12
 
@@ -14,4 +14,5 @@ export const server = createNibgateServer();
14
14
  export { NIBGATE_CONTENT_SETTING_FIELDS, createNibgateContentSettings, settingsToAccessPolicy, settingsToUnlockPolicy } from '../core/settings.js';
15
15
  export { PAYMENT_RAILS, normalizePaymentRail } from '../core/payment.js';
16
16
  export { createAdminApi, adminPageHtml } from './admin.js';
17
- export { createFileStore, createMemoryStore } from './admin-store.js';
17
+ export { createFileStore, createMemoryStore, createPostgresStore } from './admin-store.js';
18
+ export { createWebhookManager, createWebhookApi } from './webhooks.js';
@@ -0,0 +1,97 @@
1
+ import crypto from 'node:crypto';
2
+ import { serverEnv } from './env.js';
3
+
4
+ export function createWebhookManager(options = {}) {
5
+ const webhookUrl = options.webhookUrl || serverEnv('NIBGATE_WEBHOOK_URL') || '';
6
+ const webhookSecret = options.webhookSecret || serverEnv('NIBGATE_WEBHOOK_SECRET') || '';
7
+ const subscribers = new Map();
8
+
9
+ function sign(payload) {
10
+ if (!webhookSecret) return '';
11
+ return crypto.createHmac('sha256', webhookSecret).update(JSON.stringify(payload)).digest('hex');
12
+ }
13
+
14
+ function subscribe(event, url, secret = '') {
15
+ if (!subscribers.has(event)) subscribers.set(event, []);
16
+ subscribers.get(event).push({ url, secret });
17
+ return () => {
18
+ const list = subscribers.get(event) || [];
19
+ subscribers.set(event, list.filter((s) => s.url !== url));
20
+ };
21
+ }
22
+
23
+ async function emit(event, payload) {
24
+ const body = { event, timestamp: new Date().toISOString(), data: payload };
25
+ const list = subscribers.get(event) || [];
26
+ const results = [];
27
+
28
+ for (const { url, secret } of list) {
29
+ try {
30
+ const sig = secret ? crypto.createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex') : '';
31
+ const response = await fetch(url, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'content-type': 'application/json',
35
+ ...(sig ? { 'x-nibgate-webhook-signature': sig } : {})
36
+ },
37
+ body: JSON.stringify(body)
38
+ });
39
+ results.push({ url, ok: response.ok, status: response.status });
40
+ } catch (err) {
41
+ results.push({ url, ok: false, error: err.message });
42
+ }
43
+ }
44
+
45
+ if (webhookUrl) {
46
+ try {
47
+ const sig = sign(body);
48
+ const response = await fetch(webhookUrl, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'content-type': 'application/json',
52
+ ...(sig ? { 'x-nibgate-webhook-signature': sig } : {})
53
+ },
54
+ body: JSON.stringify(body)
55
+ });
56
+ results.push({ url: webhookUrl, ok: response.ok, status: response.status });
57
+ } catch (err) {
58
+ results.push({ url: webhookUrl, ok: false, error: err.message });
59
+ }
60
+ }
61
+
62
+ return results;
63
+ }
64
+
65
+ return { subscribe, emit, sign };
66
+ }
67
+
68
+ export function createWebhookApi(manager, options = {}) {
69
+ const authorize = options.authorize || ((req) => {
70
+ const key = req.headers?.['x-webhook-key'] || req.query?.key;
71
+ return key === (options.adminKey || process.env.NIBGATE_WEBHOOK_ADMIN_KEY);
72
+ });
73
+
74
+ async function handleSubscribe(req, res) {
75
+ if (!authorize(req)) return res.status(403).json({ error: 'Unauthorized' });
76
+ const { event, url, secret } = req.body || {};
77
+ if (!event || !url) return res.status(400).json({ error: 'event and url are required' });
78
+ manager.subscribe(event, url, secret || '');
79
+ return res.json({ success: true, event, url });
80
+ }
81
+
82
+ async function handleTest(req, res) {
83
+ if (!authorize(req)) return res.status(403).json({ error: 'Unauthorized' });
84
+ const results = await manager.emit('webhook_test', { message: 'Webhook test from Nibgate admin' });
85
+ return res.json({ success: true, results });
86
+ }
87
+
88
+ function router(express) {
89
+ const Route = express?.Router ? express.Router() : null;
90
+ if (!Route) return null;
91
+ Route.post('/admin/nibgate/webhooks/subscribe', handleSubscribe);
92
+ Route.post('/admin/nibgate/webhooks/test', handleTest);
93
+ return Route;
94
+ }
95
+
96
+ return { handleSubscribe, handleTest, router, manager };
97
+ }
package/src/server.d.ts CHANGED
@@ -239,4 +239,8 @@ export interface NibgateAdminApi {
239
239
  export declare function createAdminApi(options: { store: NibgateAdminStore; title?: string; authorize?: (req: Request) => boolean }): NibgateAdminApi;
240
240
  export declare function createFileStore(options?: { path?: string }): NibgateAdminStore;
241
241
  export declare function createMemoryStore(): NibgateAdminStore;
242
+ export declare function createPostgresStore(pool: Record<string, unknown>, options?: { table?: string; idColumn?: string; autoInit?: boolean }): NibgateAdminStore;
242
243
  export declare function adminPageHtml(options?: { title?: string; apiBase?: string }): string;
244
+ export declare function verifyPayment(options?: Record<string, unknown>): (req: Record<string, unknown>, res: Record<string, unknown>, next: () => void) => Promise<void>;
245
+ export declare function createWebhookManager(options?: { webhookUrl?: string; webhookSecret?: string }): { subscribe(event: string, url: string, secret?: string): () => void; emit(event: string, payload: Record<string, unknown>): Promise<Record<string, unknown>[]>; sign(payload: Record<string, unknown>): string };
246
+ export declare function createWebhookApi(manager: ReturnType<typeof createWebhookManager>, options?: { authorize?: (req: Record<string, unknown>) => boolean; adminKey?: string }): { handleSubscribe(req: Record<string, unknown>, res: Record<string, unknown>): Promise<Response>; handleTest(req: Record<string, unknown>, res: Record<string, unknown>): Promise<Response>; router(expressModule: Record<string, unknown>): unknown; manager: ReturnType<typeof createWebhookManager> };