@nibgate/sdk 0.2.0 → 0.2.2

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.
@@ -12,29 +12,29 @@ export function createAdminApi(options = {}) {
12
12
  }
13
13
 
14
14
  async function handleList(req, res) {
15
- if (!requireAdmin(req, res)) return jsonResponse({ error: 'Unauthorized' }, { status: 403 });
15
+ if (!requireAdmin(req, res)) return jsonResponse({ ok: false, error: 'Unauthorized' }, { status: 403 });
16
16
  const all = store.list();
17
- return jsonResponse({ success: true, fields: NIBGATE_CONTENT_SETTING_FIELDS, items: all });
17
+ return jsonResponse({ ok: true, fields: NIBGATE_CONTENT_SETTING_FIELDS, items: all });
18
18
  }
19
19
 
20
20
  async function handleGet(req, res) {
21
- if (!requireAdmin(req, res)) return jsonResponse({ error: 'Unauthorized' }, { status: 403 });
21
+ if (!requireAdmin(req, res)) return jsonResponse({ ok: false, error: 'Unauthorized' }, { status: 403 });
22
22
  const item = store.get(req.params.id);
23
- if (!item) return jsonResponse({ error: 'Not found' }, { status: 404 });
24
- return jsonResponse({ success: true, fields: NIBGATE_CONTENT_SETTING_FIELDS, item });
23
+ if (!item) return jsonResponse({ ok: false, error: 'Not found' }, { status: 404 });
24
+ return jsonResponse({ ok: true, fields: NIBGATE_CONTENT_SETTING_FIELDS, item });
25
25
  }
26
26
 
27
27
  async function handleUpdate(req, res) {
28
- if (!requireAdmin(req, res)) return jsonResponse({ error: 'Unauthorized' }, { status: 403 });
28
+ if (!requireAdmin(req, res)) return jsonResponse({ ok: false, error: 'Unauthorized' }, { status: 403 });
29
29
  const settings = createNibgateContentSettings(req.body || {});
30
30
  const saved = store.set(req.params.id, settings);
31
- return jsonResponse({ success: true, item: saved });
31
+ return jsonResponse({ ok: true, item: saved });
32
32
  }
33
33
 
34
34
  async function handleDelete(req, res) {
35
- if (!requireAdmin(req, res)) return jsonResponse({ error: 'Unauthorized' }, { status: 403 });
35
+ if (!requireAdmin(req, res)) return jsonResponse({ ok: false, error: 'Unauthorized' }, { status: 403 });
36
36
  store.remove(req.params.id);
37
- return jsonResponse({ success: true });
37
+ return jsonResponse({ ok: true });
38
38
  }
39
39
 
40
40
  function buildResourceFromSettings(id, settings) {
@@ -82,180 +82,18 @@ export function createAdminApi(options = {}) {
82
82
  };
83
83
  }
84
84
 
85
+ import { readFileSync } from 'node:fs';
86
+ import { fileURLToPath } from 'node:url';
87
+ import { dirname, resolve } from 'node:path';
88
+
89
+ const __dirname = dirname(fileURLToPath(import.meta.url));
90
+ const ADMIN_HTML_PATH = resolve(__dirname, 'admin-page.html');
91
+
85
92
  function adminPageHtml(options = {}) {
86
93
  const title = options.title || 'Nibgate Admin';
87
94
  const apiBase = options.apiBase || '/admin/nibgate';
88
- return `<!DOCTYPE html>
89
- <html lang="en">
90
- <head>
91
- <meta charset="UTF-8">
92
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
93
- <title>${title}</title>
94
- <style>
95
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
96
- body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f5f5f0;color:#1a1a18;padding:24px;max-width:960px;margin:0 auto}
97
- h1{font-size:28px;font-weight:700;margin-bottom:8px}
98
- p.sub{color:#666;margin-bottom:24px}
99
- .card{background:#fff;border-radius:12px;padding:20px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,0.08)}
100
- .card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
101
- .card-header h3{font-size:18px;font-weight:600}
102
- .status{font-size:12px;padding:3px 10px;border-radius:99px;font-weight:600}
103
- .status-gated{background:#fef3c7;color:#92400e}
104
- .status-open{background:#d1fae5;color:#065f46}
105
- .status-hidden{background:#fce4ec;color:#c62828}
106
- .field-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
107
- .field{display:flex;flex-direction:column;gap:4px}
108
- .field label{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#555}
109
- .field input,.field select{font-size:14px;padding:8px 10px;border:1px solid #ddd;border-radius:6px;background:#fff}
110
- .field input:focus,.field select:focus{outline:2px solid #7c9a6d;border-color:transparent}
111
- .field .checkbox-row{display:flex;align-items:center;gap:8px;padding-top:6px}
112
- .field .checkbox-row input[type="checkbox"]{width:18px;height:18px}
113
- .empty{text-align:center;padding:48px;color:#888}
114
- .toast{position:fixed;bottom:24px;right:24px;background:#1a1a18;color:#fff;padding:12px 20px;border-radius:8px;font-size:14px;opacity:0;transition:opacity 0.3s;z-index:100}
115
- .toast.show{opacity:1}
116
- .toast.error{background:#c62828}
117
- .header-bar{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}
118
- .header-bar a{color:#7c9a6d;text-decoration:none;font-size:14px;font-weight:600}
119
- .header-bar a:hover{text-decoration:underline}
120
- .help-text{font-size:12px;color:#888;margin-top:2px}
121
- </style>
122
- </head>
123
- <body>
124
- <div class="header-bar">
125
- <div>
126
- <h1>Nibgate Admin</h1>
127
- <p class="sub">Manage gating, pricing, and settings for your content.</p>
128
- </div>
129
- <a href="/nibgate.json" target="_blank">View manifest →</a>
130
- </div>
131
- <div id="app"><div class="empty">Loading resources...</div></div>
132
- <div id="toast" class="toast"></div>
133
- <script>
134
- const API = '${apiBase}';
135
- let fields = [];
136
- let items = [];
137
-
138
- async function load() {
139
- try {
140
- const res = await fetch(API + '/resources');
141
- const data = await res.json();
142
- if (!data.success) throw new Error(data.error || 'Failed to load');
143
- fields = data.fields || [];
144
- items = data.items || [];
145
- render();
146
- } catch (err) {
147
- document.getElementById('app').innerHTML = '<div class="empty">Error loading: ' + err.message + '</div>';
148
- }
149
- }
150
-
151
- function statusLabel(item) {
152
- if (item.humanAccess === 'paid') return '<span class="status status-gated">Gated</span>';
153
- if (item.humanAccess === 'blocked') return '<span class="status status-hidden">Blocked</span>';
154
- return '<span class="status status-open">Open</span>';
155
- }
156
-
157
- function render() {
158
- const app = document.getElementById('app');
159
- if (!items.length) {
160
- app.innerHTML = '<div class="empty">No resources configured yet. Add resources through the Nibgate SDK and they will appear here after the next manifest sync.</div>';
161
- return;
162
- }
163
- app.innerHTML = items.map(item => {
164
- const humanAccess = fields.find(f => f.name==='humanAccess');
165
- const agentAccess = fields.find(f => f.name==='agentAccess');
166
- const unlockMode = fields.find(f => f.name==='unlockMode');
167
- return '<div class="card" data-id="' + item.id + '">' +
168
- '<div class="card-header">' +
169
- '<h3>' + escapeHtml(item.title || item.id) + '</h3>' +
170
- statusLabel(item) +
171
- '</div>' +
172
- '<div class="field-row">' +
173
- selectField('humanAccess', 'Human access', humanAccess?.options || ['free','paid','blocked'], item.humanAccess) +
174
- selectField('agentAccess', 'Agent access', agentAccess?.options || ['free','paid','blocked'], item.agentAccess) +
175
- '</div>' +
176
- '<div class="field-row">' +
177
- selectField('unlockMode', 'Unlock mode', unlockMode?.options || ['one_time'], item.unlockMode) +
178
- selectField('paymentRail', 'Payment rail', ['gateway','transfer'], item.paymentRail || 'gateway') +
179
- '</div>' +
180
- '<div class="field-row">' +
181
- textField('price', 'Price (' + (item.currency || 'USDC') + ')', item.price) +
182
- textField('currency', 'Currency', item.currency || 'USDC') +
183
- '</div>' +
184
- '<div class="field-row">' +
185
- textField('recipient', 'Recipient wallet', item.recipient || '') +
186
- checkboxField('ratingsEnabled', 'Enable onchain ratings', item.ratingsEnabled !== false) +
187
- '</div>' +
188
- '<div class="field-row">' +
189
- selectField('type', 'Content type', ['article','music','video','image'], item.type || 'article') +
190
- checkboxField('publishToNibgate', 'Publish to discovery', item.publishToNibgate !== false) +
191
- '</div>' +
192
- '<div><button class="save-btn" onclick="save(this)">Save</button></div>' +
193
- '</div>';
194
- }).join('');
195
- }
196
-
197
- function textField(name, label, value) {
198
- return '<div class="field"><label>' + label + '</label><input type="text" name="' + name + '" value="' + escapeAttr(value || '') + '" /></div>';
199
- }
200
-
201
- function selectField(name, label, options, value) {
202
- const opts = (options || []).map(o => '<option value="' + o + '"' + (o === value ? ' selected' : '') + '>' + o + '</option>').join('');
203
- return '<div class="field"><label>' + label + '</label><select name="' + name + '">' + opts + '</select></div>';
204
- }
205
-
206
- function checkboxField(name, label, checked) {
207
- return '<div class="field"><label>' + label + '</label><div class="checkbox-row"><input type="checkbox" name="' + name + '"' + (checked ? ' checked' : '') + ' /></div></div>';
208
- }
209
-
210
- function escapeHtml(str) {
211
- const div = document.createElement('div');
212
- div.textContent = str;
213
- return div.innerHTML;
214
- }
215
-
216
- function escapeAttr(str) {
217
- return String(str).replace(/"/g,'&quot;').replace(/'/g,'&#39;');
218
- }
219
-
220
- async function save(btn) {
221
- const card = btn.closest('.card');
222
- const id = card.dataset.id;
223
- const inputs = card.querySelectorAll('[name]');
224
- const body = {};
225
- inputs.forEach(inp => {
226
- if (inp.type === 'checkbox') body[inp.name] = inp.checked;
227
- else body[inp.name] = inp.value;
228
- });
229
- btn.disabled = true;
230
- btn.textContent = 'Saving...';
231
- try {
232
- const res = await fetch(API + '/resources/' + encodeURIComponent(id), {
233
- method: 'POST',
234
- headers: { 'content-type': 'application/json' },
235
- body: JSON.stringify(body)
236
- });
237
- const data = await res.json();
238
- if (!data.success) throw new Error(data.error || 'Save failed');
239
- toast('Saved ' + (data.item.title || id), 'success');
240
- load();
241
- } catch (err) {
242
- toast(err.message, 'error');
243
- btn.disabled = false;
244
- btn.textContent = 'Save';
245
- }
246
- }
247
-
248
- function toast(msg, type) {
249
- const el = document.getElementById('toast');
250
- el.textContent = msg;
251
- el.className = 'toast ' + (type==='error'?'error':'') + ' show';
252
- setTimeout(() => { el.classList.remove('show'); }, 3000);
253
- }
254
-
255
- load();
256
- </script>
257
- </body>
258
- </html>`;
95
+ const baseHtml = readFileSync(ADMIN_HTML_PATH, 'utf8');
96
+ return baseHtml.replace('${TITLE}', title).replace('${API_BASE}', apiBase);
259
97
  }
260
98
 
261
99
  export { adminPageHtml };
@@ -132,9 +132,14 @@ export async function runCircleGatewayRequirement(request, resourceInput, option
132
132
  let statusCode = 200;
133
133
  let nextCalled = false;
134
134
  const requestHeaders = {};
135
- request.headers?.forEach?.((value, key) => {
136
- requestHeaders[key.toLowerCase()] = value;
137
- });
135
+ const sourceHeaders = request.headers || {};
136
+ if (typeof sourceHeaders.forEach === 'function') {
137
+ sourceHeaders.forEach((value, key) => { requestHeaders[key.toLowerCase()] = value; });
138
+ } else {
139
+ for (const key of Object.keys(sourceHeaders)) {
140
+ requestHeaders[key.toLowerCase()] = sourceHeaders[key];
141
+ }
142
+ }
138
143
  const req = {
139
144
  method: request.method || 'GET',
140
145
  url: resource.url || resource.path || '/',
@@ -76,13 +76,13 @@ export function createWebhookApi(manager, options = {}) {
76
76
  const { event, url, secret } = req.body || {};
77
77
  if (!event || !url) return res.status(400).json({ error: 'event and url are required' });
78
78
  manager.subscribe(event, url, secret || '');
79
- return res.json({ success: true, event, url });
79
+ return res.json({ ok: true, event, url });
80
80
  }
81
81
 
82
82
  async function handleTest(req, res) {
83
83
  if (!authorize(req)) return res.status(403).json({ error: 'Unauthorized' });
84
84
  const results = await manager.emit('webhook_test', { message: 'Webhook test from Nibgate admin' });
85
- return res.json({ success: true, results });
85
+ return res.json({ ok: true, results });
86
86
  }
87
87
 
88
88
  function router(express) {
@@ -1,39 +0,0 @@
1
- import { createRequire } from 'node:module';
2
- import path from 'node:path';
3
- import { fileURLToPath, pathToFileURL } from 'node:url';
4
-
5
- const requireFromPackage = createRequire(import.meta.url);
6
- const requireFromCwd = createRequire(`${process.cwd()}/package.json`);
7
- const packageSourceDir = path.dirname(fileURLToPath(import.meta.url));
8
- const workspaceNodeModulesDir = path.resolve(packageSourceDir, '../../../node_modules/.pnpm/node_modules');
9
- const nativeImport = new Function('specifier', 'return import(specifier)');
10
-
11
- export async function runtimeImportPackage(specifier) {
12
- try {
13
- return await nativeImport(specifier);
14
- } catch (_error) {
15
- try {
16
- return await nativeImport(requireFromPackage.resolve(specifier));
17
- } catch (_packageError) {
18
- try {
19
- return await nativeImport(requireFromCwd.resolve(specifier));
20
- } catch (_cwdError) {
21
- if (specifier === '@circle-fin/x402-batching/client') {
22
- try {
23
- return await nativeImport(`${process.cwd()}/node_modules/.pnpm/node_modules/@circle-fin/x402-batching/dist/client/index.mjs`);
24
- } catch {
25
- return nativeImport(pathToFileURL(path.join(workspaceNodeModulesDir, '@circle-fin/x402-batching/dist/client/index.mjs')).href);
26
- }
27
- }
28
- if (specifier === '@circle-fin/x402-batching/server') {
29
- try {
30
- return await nativeImport(`${process.cwd()}/node_modules/.pnpm/node_modules/@circle-fin/x402-batching/dist/server/index.mjs`);
31
- } catch {
32
- return nativeImport(pathToFileURL(path.join(workspaceNodeModulesDir, '@circle-fin/x402-batching/dist/server/index.mjs')).href);
33
- }
34
- }
35
- return nativeImport(`${process.cwd()}/node_modules/.pnpm/node_modules/${specifier}`);
36
- }
37
- }
38
- }
39
- }