enigmatic 0.34.0 → 0.35.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/README.md +61 -95
- package/bin/enigmatic.js +3 -0
- package/client/public/AGENTS.md +314 -0
- package/{public → client/public}/client.js +13 -6
- package/client/public/index.html +197 -0
- package/clientserver.png +0 -0
- package/package.json +7 -9
- package/server/bun-server.js +119 -0
- package/__tests__/e2.test.js +0 -340
- package/__tests__/jest.config.js +0 -7
- package/__tests__/jest.setup.js +0 -9
- package/beemap.js +0 -47
- package/bun-server.js +0 -122
- package/public/index.html +0 -48
- package/public/index2.html +0 -10
- /package/{public → client/public}/custom.js +0 -0
package/__tests__/jest.setup.js
DELETED
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/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>
|
package/public/index2.html
DELETED
|
File without changes
|