enigmatic 0.34.0 → 0.36.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.
@@ -1,340 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
-
4
- // Load client.js and custom.js into jsdom
5
- const clientCode = fs.readFileSync(path.join(__dirname, '../public/client.js'), 'utf8')
6
- const customCode = fs.readFileSync(path.join(__dirname, '../public/custom.js'), 'utf8')
7
-
8
- describe('client.js', () => {
9
- beforeEach(() => {
10
- // Reset DOM
11
- global.document.body.innerHTML = ''
12
- global.document.head.innerHTML = ''
13
-
14
- // Set api_url
15
- global.window.api_url = 'https://localhost:3000'
16
-
17
- // Clear window properties that might have descriptors
18
- try {
19
- delete global.window.custom
20
- delete global.window.state
21
- delete global.window.$
22
- delete global.window.$$
23
- delete global.window.$c
24
- delete global.window.get
25
- delete global.window.set
26
- delete global.window.put
27
- delete global.window.delete
28
- delete global.window.purge
29
- delete global.window.list
30
- delete global.window.login
31
- delete global.window.logout
32
- delete global.window.download
33
- delete global.window.initCustomElements
34
- } catch (e) {
35
- // Ignore errors
36
- }
37
-
38
- // Execute client.js code first (sets up Proxy)
39
- eval(clientCode)
40
- // Execute custom.js (defines window.custom components)
41
- eval(customCode)
42
-
43
- // Wait for initialization
44
- return new Promise(resolve => setTimeout(resolve, 100))
45
- })
46
-
47
- describe('$ and $$ selectors', () => {
48
- test('$ selects single element', () => {
49
- global.document.body.innerHTML = '<div id="test">Hello</div>'
50
- expect(window.$('#test').textContent).toBe('Hello')
51
- })
52
-
53
- test('$$ selects multiple elements', () => {
54
- global.document.body.innerHTML = '<div class="item">1</div><div class="item">2</div>'
55
- const items = window.$$('.item')
56
- expect(items.length).toBe(2)
57
- expect(items[0].textContent).toBe('1')
58
- expect(items[1].textContent).toBe('2')
59
- })
60
-
61
- test('$ returns null for non-existent element', () => {
62
- global.document.body.innerHTML = '<div>Test</div>'
63
- expect(window.$('#nonexistent')).toBeNull()
64
- })
65
-
66
- test('$$ returns empty NodeList for non-existent elements', () => {
67
- global.document.body.innerHTML = '<div>Test</div>'
68
- expect(window.$$('.nonexistent').length).toBe(0)
69
- })
70
- })
71
-
72
- describe('window API functions', () => {
73
- let originalFetch
74
- let originalLocation
75
-
76
- beforeEach(() => {
77
- originalFetch = global.fetch
78
- global.fetch = jest.fn()
79
- originalLocation = global.window.location
80
- delete global.window.location
81
- global.window.location = { href: '' }
82
- })
83
-
84
- afterEach(() => {
85
- global.fetch = originalFetch
86
- global.window.location = originalLocation
87
- })
88
-
89
- test('window.get makes GET request with encoded key', async () => {
90
- global.fetch.mockResolvedValueOnce({
91
- ok: true,
92
- json: () => Promise.resolve({ value: 'test' })
93
- })
94
-
95
- const result = await window.get('test key')
96
-
97
- expect(global.fetch).toHaveBeenCalledWith(
98
- `${window.api_url}/test%20key`,
99
- expect.objectContaining({ method: 'GET' })
100
- )
101
- expect(result).toEqual({ value: 'test' })
102
- })
103
-
104
- test('window.set makes POST request with string value', async () => {
105
- global.fetch.mockResolvedValueOnce({
106
- ok: true,
107
- json: () => Promise.resolve({ status: 'saved' })
108
- })
109
-
110
- const result = await window.set('test', 'value')
111
-
112
- expect(global.fetch).toHaveBeenCalledWith(
113
- `${window.api_url}/test`,
114
- expect.objectContaining({
115
- method: 'POST',
116
- body: 'value'
117
- })
118
- )
119
- expect(result).toEqual({ status: 'saved' })
120
- })
121
-
122
- test('window.set stringifies object values', async () => {
123
- global.fetch.mockResolvedValueOnce({
124
- ok: true,
125
- json: () => Promise.resolve({ status: 'saved' })
126
- })
127
-
128
- await window.set('test', { key: 'value' })
129
-
130
- expect(global.fetch).toHaveBeenCalledWith(
131
- `${window.api_url}/test`,
132
- expect.objectContaining({
133
- method: 'POST',
134
- body: JSON.stringify({ key: 'value' })
135
- })
136
- )
137
- })
138
-
139
- test('window.delete makes DELETE request', async () => {
140
- global.fetch.mockResolvedValueOnce({
141
- ok: true,
142
- json: () => Promise.resolve({ status: 'deleted' })
143
- })
144
-
145
- const result = await window.delete('test')
146
-
147
- expect(global.fetch).toHaveBeenCalledWith(
148
- `${window.api_url}/test`,
149
- expect.objectContaining({
150
- method: 'DELETE'
151
- })
152
- )
153
- expect(result).toEqual({ status: 'deleted' })
154
- })
155
-
156
- test('window.put makes PUT request with string body', async () => {
157
- global.fetch.mockResolvedValueOnce({
158
- ok: true,
159
- json: () => Promise.resolve({ status: 'saved' })
160
- })
161
-
162
- const result = await window.put('test', 'content')
163
-
164
- expect(global.fetch).toHaveBeenCalledWith(
165
- `${window.api_url}/test`,
166
- expect.objectContaining({
167
- method: 'PUT',
168
- body: 'content'
169
- })
170
- )
171
- expect(result).toEqual({ status: 'saved' })
172
- })
173
-
174
- test('window.put stringifies object body', async () => {
175
- global.fetch.mockResolvedValueOnce({
176
- ok: true,
177
- json: () => Promise.resolve({ status: 'saved' })
178
- })
179
-
180
- await window.put('test', { data: 'value' })
181
-
182
- expect(global.fetch).toHaveBeenCalledWith(
183
- `${window.api_url}/test`,
184
- expect.objectContaining({
185
- method: 'PUT',
186
- body: JSON.stringify({ data: 'value' })
187
- })
188
- )
189
- })
190
-
191
- test('window.put handles Blob body', async () => {
192
- const blob = new Blob(['test'], { type: 'text/plain' })
193
- global.fetch.mockResolvedValueOnce({
194
- ok: true,
195
- json: () => Promise.resolve({ status: 'saved' })
196
- })
197
-
198
- await window.put('test', blob)
199
-
200
- expect(global.fetch).toHaveBeenCalledWith(
201
- `${window.api_url}/test`,
202
- expect.objectContaining({
203
- method: 'PUT',
204
- body: blob
205
- })
206
- )
207
- })
208
-
209
- test('window.purge makes PURGE request', async () => {
210
- global.fetch.mockResolvedValueOnce({
211
- ok: true,
212
- json: () => Promise.resolve({ status: 'deleted' })
213
- })
214
-
215
- const result = await window.purge('test')
216
-
217
- expect(global.fetch).toHaveBeenCalledWith(
218
- `${window.api_url}/test`,
219
- expect.objectContaining({
220
- method: 'PURGE'
221
- })
222
- )
223
- expect(result).toEqual({ status: 'deleted' })
224
- })
225
-
226
- test('window.list makes PROPFIND request to base URL', async () => {
227
- global.fetch.mockResolvedValueOnce({
228
- ok: true,
229
- json: () => Promise.resolve([{ name: 'file1' }])
230
- })
231
-
232
- const result = await window.list()
233
-
234
- expect(global.fetch).toHaveBeenCalledWith(
235
- `${window.api_url}/`,
236
- expect.objectContaining({
237
- method: 'PROPFIND'
238
- })
239
- )
240
- expect(result).toEqual([{ name: 'file1' }])
241
- })
242
-
243
- test('window.login redirects to /login', () => {
244
- window.login()
245
- expect(global.window.location.href).toBe(`${window.api_url}/login`)
246
- })
247
-
248
- test('window.logout redirects to /logout', () => {
249
- window.logout()
250
- expect(global.window.location.href).toBe(`${window.api_url}/logout`)
251
- })
252
- })
253
-
254
- describe('window.custom components', () => {
255
- test('hello-world component is a function', () => {
256
- expect(typeof window.custom['hello-world']).toBe('function')
257
- })
258
-
259
- test('hello-world component renders correctly', () => {
260
- const result = window.custom['hello-world']('Test')
261
- expect(result).toBe('Hello Test')
262
- })
263
-
264
- test('hello-world-2 component has prop and render methods', () => {
265
- expect(window.custom['hello-world-2']).toBeDefined()
266
- expect(typeof window.custom['hello-world-2'].prop).toBe('function')
267
- expect(typeof window.custom['hello-world-2'].render).toBe('function')
268
- })
269
-
270
- test('hello-world-2 component prop works', () => {
271
- const result = window.custom['hello-world-2'].prop('Test')
272
- expect(result).toBe('Test World')
273
- })
274
-
275
- test('hello-world-2 component render works', () => {
276
- const result = window.custom['hello-world-2'].render('Test')
277
- expect(result).toBe('Test World')
278
- })
279
- })
280
-
281
-
282
- describe('window.state proxy', () => {
283
- test('state.set updates DOM elements with data attribute', async () => {
284
- global.document.body.innerHTML = '<hello-world data="name"></hello-world>'
285
-
286
- window.state.name = 'John'
287
- await new Promise(resolve => setTimeout(resolve, 50))
288
-
289
- const el = global.document.querySelector('hello-world')
290
- expect(el.innerHTML).toBe('Hello John')
291
- })
292
-
293
- test('state.set updates multiple elements with same data attribute', async () => {
294
- global.document.body.innerHTML = '<hello-world data="name"></hello-world><hello-world data="name"></hello-world>'
295
-
296
- window.state.name = 'Jane'
297
- await new Promise(resolve => setTimeout(resolve, 50))
298
-
299
- const els = global.document.querySelectorAll('hello-world')
300
- expect(els[0].innerHTML).toBe('Hello Jane')
301
- expect(els[1].innerHTML).toBe('Hello Jane')
302
- })
303
-
304
- test('state.set works with object components', async () => {
305
- global.document.body.innerHTML = '<hello-world-2 data="test"></hello-world-2>'
306
-
307
- window.state.test = 'Hello'
308
- await new Promise(resolve => setTimeout(resolve, 50))
309
-
310
- const el = global.document.querySelector('hello-world-2')
311
- expect(el.innerHTML).toBe('Hello World')
312
- })
313
-
314
- test('state.set stores value in proxy object', () => {
315
- window.state.test = 'value'
316
- expect(window.state.test).toBe('value')
317
- })
318
-
319
- test('state.set handles multiple properties', async () => {
320
- global.document.body.innerHTML = '<hello-world data="a"></hello-world><hello-world data="b"></hello-world>'
321
-
322
- window.state.a = 'A'
323
- window.state.b = 'B'
324
- await new Promise(resolve => setTimeout(resolve, 50))
325
-
326
- expect(global.document.querySelector('[data="a"]').innerHTML).toBe('Hello A')
327
- expect(global.document.querySelector('[data="b"]').innerHTML).toBe('Hello B')
328
- })
329
-
330
- test('state.set does not update elements without matching data attribute', async () => {
331
- global.document.body.innerHTML = '<hello-world data="name"></hello-world><div data="other">Original</div>'
332
-
333
- window.state.name = 'John'
334
- await new Promise(resolve => setTimeout(resolve, 50))
335
-
336
- expect(global.document.querySelector('[data="name"]').innerHTML).toBe('Hello John')
337
- expect(global.document.querySelector('[data="other"]').innerHTML).toBe('Original')
338
- })
339
- })
340
- })
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- testEnvironment: 'jsdom',
3
- testMatch: ['**/__tests__/**/*.test.js'],
4
- setupFilesAfterEnv: ['<rootDir>/__tests__/jest.setup.js'],
5
- testTimeout: 10000,
6
- rootDir: require('path').resolve(__dirname, '..')
7
- }
@@ -1,9 +0,0 @@
1
- // Mock window.components if not defined
2
- if (typeof window !== 'undefined' && !window.components) {
3
- window.components = {}
4
- }
5
-
6
- // Add fetch polyfill for jsdom
7
- if (typeof global.fetch === 'undefined') {
8
- global.fetch = require('whatwg-fetch').fetch
9
- }
package/beemap.js DELETED
@@ -1,47 +0,0 @@
1
- class BeeMap extends Map {
2
- constructor(jsonlFile, timems) {
3
- super();
4
- this.jsonlFile = jsonlFile;
5
- this.timems = timems;
6
- this.intervalId = null;
7
-
8
- // Load existing data
9
- this.load();
10
-
11
- // Set up interval to save
12
- if (!timems) return;
13
- this.intervalId = setInterval(() => {
14
- this.save();
15
- }, timems);
16
- }
17
-
18
- async load() {
19
- const file = Bun.file(this.jsonlFile);
20
- if (!(await file.exists())) return;
21
-
22
- const text = await file.text();
23
- const lines = text.trim().split('\n').filter(line => line.length > 0);
24
-
25
- for (const line of lines) {
26
- const [key, value] = JSON.parse(line);
27
- super.set(key, value);
28
- }
29
- }
30
-
31
- async save() {
32
- const lines = [];
33
- for (const [key, value] of this) {
34
- lines.push(JSON.stringify([key, value]));
35
- }
36
- await Bun.write(this.jsonlFile, lines.join('\n') + '\n');
37
- }
38
-
39
- destroy() {
40
- if (this.intervalId) {
41
- clearInterval(this.intervalId);
42
- this.intervalId = null;
43
- }
44
- }
45
- }
46
-
47
- export { BeeMap };
package/bun-server.js DELETED
@@ -1,122 +0,0 @@
1
- import { S3Client } from "bun";
2
- import { BeeMap } from "./beemap.js";
3
-
4
- const sessions = new BeeMap("sessions.jsonl", 20000), beeMaps = {};
5
- const s3 = new S3Client({
6
- accessKeyId: Bun.env.CLOUDFLARE_ACCESS_KEY_ID,
7
- secretAccessKey: Bun.env.CLOUDFLARE_SECRET_ACCESS_KEY,
8
- bucket: Bun.env.CLOUDFLARE_BUCKET_NAME,
9
- endpoint: Bun.env.CLOUDFLARE_PUBLIC_URL
10
- });
11
-
12
- const json = (data, status = 200, extraHeaders = {}) => new Response(JSON.stringify(data), {
13
- status, headers: {
14
- "Access-Control-Allow-Origin": "*",
15
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PURGE, PROPFIND, DOWNLOAD, OPTIONS",
16
- "Access-Control-Allow-Headers": "Content-Type, Authorization, Cookie, X-HTTP-Method-Override",
17
- "Access-Control-Allow-Credentials": "true",
18
- "Content-Type": "application/json",
19
- ...extraHeaders
20
- }
21
- });
22
-
23
- const redirect = (url, cookie = null) => new Response(null, {
24
- status: 302,
25
- headers: { Location: url, ...(cookie && { "Set-Cookie": cookie }) }
26
- });
27
-
28
- export default {
29
- async fetch(req) {
30
- const url = new URL(req.url), key = url.pathname.slice(1);
31
- const token = req.headers.get("Cookie")?.match(/token=([^;]+)/)?.[1];
32
- const user = token ? sessions.get(token) : null;
33
-
34
- if (req.method === "OPTIONS") return json(null, 204);
35
-
36
- // Serve static files from public folder
37
- if (req.method === 'GET') {
38
- const file = Bun.file(`./public${url.pathname === '/' ? '/index.html' : url.pathname}`);
39
- if (await file.exists()) return new Response(file);
40
- }
41
-
42
- if (url.pathname === '/login') {
43
- return Response.redirect(`https://${Bun.env.AUTH0_DOMAIN}/authorize?${new URLSearchParams({
44
- response_type: "code", client_id: Bun.env.AUTH0_CLIENT_ID, redirect_uri: cb, scope: "openid email profile"
45
- })}`);
46
- }
47
-
48
- if (url.pathname === '/callback') {
49
- const code = url.searchParams.get("code");
50
- if (!code) return json({ error: 'No code' }, 400);
51
- const tRes = await fetch(`https://${Bun.env.AUTH0_DOMAIN}/oauth/token`, {
52
- method: "POST", headers: { "content-type": "application/json" },
53
- body: JSON.stringify({
54
- grant_type: "authorization_code", client_id: Bun.env.AUTH0_CLIENT_ID,
55
- client_secret: Bun.env.AUTH0_CLIENT_SECRET, code, redirect_uri: `${url.origin}/callback`
56
- })
57
- });
58
- if (!tRes.ok) return json({ error: 'Auth error' }, 401);
59
- const tokens = await tRes.json();
60
- const userInfo = await (await fetch(`https://${Bun.env.AUTH0_DOMAIN}/userinfo`, {
61
- headers: { Authorization: `Bearer ${tokens.access_token}` }
62
- })).json();
63
- const session = crypto.randomUUID();
64
- sessions.set(session, {
65
- ...userInfo,
66
- login_time: new Date().toISOString(),
67
- access_token_expires_at: tokens.expires_in ? new Date(Date.now() + tokens.expires_in * 1000).toISOString() : null
68
- });
69
- return redirect(url.origin, `token=${session}; HttpOnly; Path=/; Secure; SameSite=Lax; Max-Age=86400`);
70
- }
71
-
72
- if (!token || !user) return json({ error: 'Unauthorized' }, 401);
73
-
74
- if (url.pathname === '/logout') {
75
- sessions.delete(token);
76
- return redirect(url.origin, "token=; Max-Age=0; Path=/");
77
- }
78
-
79
- // Initialize user's beeMap if needed
80
- if (!beeMaps[user.sub]) beeMaps[user.sub] = new BeeMap(`kv_${user.sub}.jsonl`, 20000);
81
-
82
- switch (req.method) {
83
- case 'GET': return json(beeMaps[user.sub].get(key) || null);
84
- case 'POST':
85
- const value = await req.text();
86
- beeMaps[user.sub].set(key, (() => { try { return JSON.parse(value); } catch { return value; } })());
87
- return json({ key, value });
88
- case 'DELETE':
89
- beeMaps[user.sub].delete(key);
90
- return json({ status: "Deleted" });
91
- case 'PUT':
92
- await s3.write(`${user.sub}/${key}`, req.body);
93
- return json({ status: "Saved to R2" });
94
- case 'PURGE':
95
- await s3.delete(`${user.sub}/${key}`);
96
- return json({ status: "Deleted from R2" });
97
- case 'PROPFIND':
98
- const list = await s3.list({ prefix: `${user.sub}/` });
99
- const items = Array.isArray(list) ? list : (list?.contents || []);
100
- return json(items.map(item => ({
101
- name: item.key?.split('/').pop() || item.name || item.Key,
102
- lastModified: item.lastModified || item.LastModified,
103
- size: item.size || item.Size || 0
104
- })));
105
- case 'PATCH':
106
- try {
107
- const exists = await s3.exists(`${user.sub}/${key}`);
108
- if (!exists) return json({ error: 'File not found' }, 404);
109
- const file = await s3.file(`${user.sub}/${key}`);
110
- if (!file) return json({ error: 'File not found' }, 404);
111
- return new Response(file.stream(), { headers: file.headers });
112
- } catch (err) {
113
- console.error('Download error:', err);
114
- return json({ error: 'File not found', details: err.message }, 404);
115
- }
116
- default: return json({ error: 'Method not allowed' }, 405);
117
- }
118
- },
119
-
120
- port: 3000,
121
- tls: { cert: Bun.file("cert.pem"), key: Bun.file("key.pem") }
122
- };
package/public/client.js DELETED
@@ -1,118 +0,0 @@
1
- const D = document, W = window, Enc = encodeURIComponent;
2
-
3
- // 1. Unified Render Logic (Handles both State & Custom Elements)
4
- const ren = async (el, v) => {
5
- const f = W.custom?.[el.tagName.toLowerCase()];
6
- if (f) {
7
- const dataAttr = el.getAttribute('data');
8
- const val = v !== undefined ? v : (dataAttr ? W.state[dataAttr] : undefined);
9
- try {
10
- if (f.render) {
11
- el.innerHTML = await f.render.call(f, val);
12
- } else if (typeof f === 'function') {
13
- el.innerHTML = await f(val);
14
- }
15
- } catch(e) { console.error(e) }
16
- }
17
- };
18
-
19
- // 2. Proxies setup
20
- const cProx = new Proxy({}, {
21
- set(t, p, v) {
22
- t[p] = v;
23
- setTimeout(() => {
24
- if (W.$$ && D.body) {
25
- W.$$(p).forEach(el => ren(el));
26
- }
27
- }, 0);
28
- return true;
29
- }
30
- });
31
- Object.defineProperty(W, 'custom', {
32
- get: () => cProx,
33
- set: v => {
34
- Object.keys(v || {}).forEach(k => cProx[k] = v[k]);
35
- // Defer initialization to ensure DOM and functions are ready
36
- setTimeout(() => {
37
- if (W.initCustomElements && D.body) W.initCustomElements();
38
- }, 50);
39
- },
40
- configurable: true
41
- });
42
-
43
- const sProx = new Proxy({}, {
44
- set(o, p, v) {
45
- o[p] = v;
46
- W.$$(`[data="${p}"]`).forEach(el => ren(el, v));
47
- return true;
48
- }
49
- });
50
-
51
- // 3. API & DOM Helpers
52
- const req = (m, k, b) => fetch(`${W.api_url}/${k ? Enc(k) : ''}`, {
53
- method: m, body: b instanceof Blob || typeof b === 'string' ? b : JSON.stringify(b)
54
- });
55
-
56
- Object.assign(W, {
57
- $: s => D.querySelector(s),
58
- $$: s => D.querySelectorAll(s),
59
- $c: s => $0.closest(s),
60
- state: sProx,
61
- get: k => req('GET', k).then(r => r.json()),
62
- set: (k, v) => req('POST', k, v).then(r => r.json()),
63
- put: (k, v) => req('PUT', k, v).then(r => r.json()),
64
- delete: k => req('DELETE', k).then(r => r.json()),
65
- purge: k => req('PURGE', k).then(r => r.json()),
66
- list: () => req('PROPFIND').then(r => r.json()),
67
- login: () => W.location.href = `${W.api_url}/login`,
68
- logout: () => W.location.href = `${W.api_url}/logout`,
69
- download: async (k) => {
70
- const r = await req('PATCH', k);
71
- if (!r.ok) throw new Error('Download failed');
72
- const a = D.createElement('a');
73
- a.href = URL.createObjectURL(await r.blob());
74
- a.download = k;
75
- a.click();
76
- URL.revokeObjectURL(a.href);
77
- },
78
- initCustomElements: () => {
79
- if (!D.body) return;
80
- Object.keys(W.custom || {}).forEach(t => {
81
- const elements = W.$$(t);
82
- if (elements.length > 0) {
83
- elements.forEach(el => ren(el));
84
- }
85
- });
86
- }
87
- });
88
-
89
- // 4. Initialization & Observers
90
- const boot = () => {
91
- if (W.initCustomElements) {
92
- // Run immediately and also after a short delay to catch any elements added during script execution
93
- W.initCustomElements();
94
- setTimeout(() => W.initCustomElements(), 10);
95
- }
96
- if (D.body) {
97
- new MutationObserver((mutations) => {
98
- mutations.forEach(m => {
99
- m.addedNodes.forEach(node => {
100
- if (node.nodeType === 1) { // Element node
101
- const tag = node.tagName?.toLowerCase();
102
- if (tag && W.custom?.[tag]) ren(node);
103
- // Also check children
104
- node.querySelectorAll && Array.from(node.querySelectorAll('*')).forEach(child => {
105
- const childTag = child.tagName?.toLowerCase();
106
- if (childTag && W.custom?.[childTag]) ren(child);
107
- });
108
- }
109
- });
110
- });
111
- }).observe(D.body, { childList: true, subtree: true });
112
- }
113
- };
114
- if (D.readyState === 'loading') {
115
- D.addEventListener('DOMContentLoaded', boot);
116
- } else {
117
- setTimeout(boot, 0);
118
- }
package/public/index.html DELETED
@@ -1,48 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>API Test</title>
7
- <script>
8
- window.api_url = 'https://localhost:3000';
9
- </script>
10
- <script src="custom.js"></script>
11
- <script src="client.js"></script>
12
- <style>
13
- body { font-family: sans-serif; max-width: 800px; margin: 20px auto; padding: 20px; }
14
- .section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
15
- input, button { margin: 5px; padding: 8px; }
16
- input { width: 200px; }
17
- button { cursor: pointer; }
18
- #result { margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 3px; white-space: pre-wrap; }
19
- .error { color: red; }
20
- .success { color: green; }
21
- </style>
22
- </head>
23
- <body>
24
- <h1>Server API Test</h1>
25
-
26
- <div class="section">
27
- <h2>KV Storage</h2>
28
- <input type="text" id="kv-key" placeholder="Key" value="test-key">
29
- <input type="text" id="kv-value" placeholder="Value" value="test-value">
30
- <button onclick="window.set(document.querySelector('#kv-key').value, document.querySelector('#kv-value').value).then(r => window.$('#result').textContent = JSON.stringify(r)).catch(err => window.$('#result').textContent = 'Error: ' + err.message)">POST</button>
31
- <button onclick="window.get(document.querySelector('#kv-key').value).then(r => window.$('#result').textContent = JSON.stringify(r)).catch(err => window.$('#result').textContent = 'Error: ' + err.message)">GET</button>
32
- <button onclick="window.delete(document.querySelector('#kv-key').value).then(r => window.$('#result').textContent = JSON.stringify(r)).catch(err => window.$('#result').textContent = 'Error: ' + err.message)">DELETE</button>
33
- </div>
34
-
35
- <div class="section">
36
- <h2>R2 Storage</h2>
37
- <file-widget></file-widget>
38
- </div>
39
-
40
- <div class="section">
41
- <h2>Auth</h2>
42
- <button onclick="window.login()">Login</button>
43
- <button onclick="window.logout()">Logout</button>
44
- </div>
45
-
46
- <pre id="result"></pre>
47
- </body>
48
- </html>