@nibgate/sdk 0.1.8 → 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.8",
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",
@@ -556,6 +556,66 @@ export function createOnchainRating(resource, options = {}) {
556
556
  return controller;
557
557
  }
558
558
 
559
+ export function mountRatingUI(resource, options = {}) {
560
+ const item = createGate(resource, options.gateOptions || {});
561
+ const win = browserWindow();
562
+ if (!win) return null;
563
+ const target = typeof options.target === 'string' ? win.document.querySelector(options.target) : options.target;
564
+ if (!target) return null;
565
+
566
+ const stars = [1, 2, 3, 4, 5];
567
+ let selectedRating = 0;
568
+
569
+ const container = win.document.createElement('div');
570
+ container.className = 'nibgate-rating-ui';
571
+ container.style.cssText = 'display:flex;align-items:center;gap:4px;padding:8px 0';
572
+
573
+ const starButtons = stars.map((value) => {
574
+ const btn = win.document.createElement('button');
575
+ btn.type = 'button';
576
+ btn.dataset.nibgateRatingValue = String(value);
577
+ btn.setAttribute('aria-label', `${value} star${value > 1 ? 's' : ''}`);
578
+ btn.innerHTML = '☆';
579
+ btn.style.cssText = 'background:none;border:none;font-size:24px;cursor:pointer;color:#ccc;transition:color 0.15s;padding:2px;line-height:1';
580
+ btn.addEventListener('mouseenter', () => {
581
+ starButtons.forEach((b, i) => b.style.color = i < value ? '#f5b342' : '#ccc');
582
+ });
583
+ btn.addEventListener('mouseleave', () => {
584
+ starButtons.forEach((b, i) => b.style.color = i < selectedRating ? '#f5b342' : '#ccc');
585
+ });
586
+ btn.addEventListener('click', () => {
587
+ selectedRating = value;
588
+ starButtons.forEach((b, i) => b.style.color = i < value ? '#f5b342' : '#ccc');
589
+ rate(item.resource, { rating: value }).catch(() => {});
590
+ });
591
+ container.appendChild(btn);
592
+ return btn;
593
+ });
594
+
595
+ const statusEl = win.document.createElement('span');
596
+ statusEl.style.cssText = 'font-size:13px;color:#888;margin-left:8px';
597
+ statusEl.textContent = options.label || 'Rate this content';
598
+ container.appendChild(statusEl);
599
+
600
+ target.appendChild(container);
601
+
602
+ function rate(r, input = {}) {
603
+ return item.rate({ ...input, rating: r });
604
+ }
605
+
606
+ function setRating(value) {
607
+ selectedRating = value;
608
+ starButtons.forEach((b, i) => b.style.color = i < value ? '#f5b342' : '#ccc');
609
+ }
610
+
611
+ return {
612
+ resource: item.resource,
613
+ container,
614
+ setRating,
615
+ rate
616
+ };
617
+ }
618
+
559
619
  export async function payAndUnlockResource(resource, options = {}) {
560
620
  const item = createGate(resource, options.gateOptions || {});
561
621
  const payPath = options.payPath || item.resource.payPath || '/api/nibgate/pay';
@@ -690,6 +750,9 @@ export function createNibgate(defaults = {}) {
690
750
  createOnchainRating(resource, options = {}) {
691
751
  return createOnchainRating(resourceWithDefaults(resource), options);
692
752
  },
753
+ mountRatingUI(resource, options = {}) {
754
+ return mountRatingUI(resourceWithDefaults(resource), options);
755
+ },
693
756
  payAndUnlockResource(resource, options = {}) {
694
757
  return payAndUnlockResource(resourceWithDefaults(resource), options);
695
758
  },
package/src/index.d.ts CHANGED
@@ -114,6 +114,7 @@ export type NibgateClient = {
114
114
  createEvmGatewayUnlock(resource: NibgateResource | string, options?: NibgateEvmGatewayUnlockOptions): NibgateEvmGatewayUnlockController;
115
115
  rateContentOnchain(resource: NibgateResource | string, options: NibgateOnchainRatingOptions): Promise<NibgateOnchainRatingResult>;
116
116
  createOnchainRating(resource: NibgateResource | string, options?: NibgateOnchainRatingUiOptions): NibgateOnchainRatingController;
117
+ mountRatingUI(resource: NibgateResource | string, options?: NibgateRatingUiOptions): NibgateRatingUiController | null;
117
118
  payAndUnlockResource(resource: NibgateResource | string, options?: NibgatePaymentOptions): Promise<NibgatePaymentResult>;
118
119
  setupResourcePage(resource: NibgateResource | string, options?: NibgatePageSetupOptions): NibgateGate;
119
120
  ratingMessage(resource: NibgateResource | string, rating?: NibgateRating | number, options?: Record<string, unknown>): string;
@@ -430,5 +431,18 @@ export declare function ratingMessage(resource: NibgateResource | string, rating
430
431
  export declare function createNibgate(defaults?: { resource?: NibgateResource }): NibgateClient;
431
432
  export declare const nibgate: NibgateClient;
432
433
 
434
+ export interface NibgateRatingUiOptions {
435
+ target?: string | HTMLElement;
436
+ label?: string;
437
+ gateOptions?: Record<string, unknown>;
438
+ }
439
+ export interface NibgateRatingUiController {
440
+ resource: NibgateResource;
441
+ container: HTMLElement;
442
+ setRating(value: number): void;
443
+ rate(resource: NibgateResource | string, input?: Record<string, unknown>): ReturnType<typeof rateContentOnchain>;
444
+ }
445
+ export declare function mountRatingUI(resource: NibgateResource | string, options?: NibgateRatingUiOptions): NibgateRatingUiController | null;
446
+
433
447
  export declare function createTransferCheckout(resource: NibgateResource | string, options: NibgateTransferCheckoutOptions): NibgateTransferCheckout;
434
448
  export declare function payWithTransfer(resource: NibgateResource | string, options: NibgateTransferCheckoutOptions & NibgateAccessCheckOptions): Promise<NibgateAccessCheckResult>;
@@ -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> };