Pepguy commited on
Commit
fc2332b
·
verified ·
1 Parent(s): 1b905d9

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +129 -50
app.js CHANGED
@@ -1,44 +1,61 @@
 
 
 
 
1
  const events = [];
2
 
3
  const html = `
4
- <!DOCTYPE html>
5
- <html>
6
- <head>
7
- <title>Timed Event Scheduler</title>
8
- </head>
9
- <body>
10
- <h1>Schedule Timed Event</h1>
11
- <form onsubmit="submitEvent(event)">
12
- <label>ID: <input type="text" id="id" required /></label><br />
13
- <label>Delay (sec): <input type="number" id="delay" required /></label><br />
14
- <label>Message: <input type="text" id="message" /></label><br />
15
- <button type="submit">Submit</button>
16
- </form>
17
- <script>
18
- async function submitEvent(e) {
19
- e.preventDefault();
20
- const id = document.getElementById('id').value;
21
- const delay = parseInt(document.getElementById('delay').value) * 1000;
22
- const message = document.getElementById('message').value;
23
-
24
- const res = await fetch('/events', {
25
- method: 'POST',
26
- headers: { 'Content-Type': 'application/json' },
27
- body: JSON.stringify({
28
- id,
29
- timestamp: Date.now() + delay,
30
- data: { message }
31
- })
32
- });
 
 
 
 
 
 
 
 
33
 
34
- alert(await res.text());
35
- }
36
- </script>
37
- </body>
38
- </html>
 
 
 
 
 
39
  `;
40
 
41
- Bun.serve({
42
  port: 7860,
43
  fetch: async (req) => {
44
  const url = new URL(req.url);
@@ -47,38 +64,100 @@ Bun.serve({
47
  return new Response(html, { headers: { "Content-Type": "text/html" } });
48
  }
49
 
 
50
  if (req.method === "POST" && url.pathname === "/events") {
51
  try {
52
  const body = await req.json();
53
- const { id, timestamp, data } = body;
54
 
55
- if (!id || !timestamp) {
56
- return new Response("Missing 'id' or 'timestamp'", { status: 400 });
57
  }
58
 
59
- events.push({ id, timestamp: Number(timestamp), data });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  return new Response("Event scheduled", { status: 200 });
61
- } catch {
62
  return new Response("Invalid JSON", { status: 400 });
63
  }
64
  }
65
 
 
 
 
 
 
 
 
 
 
66
  return new Response("Not Found", { status: 404 });
67
  }
68
  });
69
 
70
- // Non-blocking timer loop
71
- setInterval(() => {
72
  const now = Date.now();
73
  const due = events.filter(e => e.timestamp <= now);
74
- if (due.length > 0) {
75
- due.forEach(e => {
76
- console.log(`⏰ Event due: [${e.id}]`, e.data);
77
- });
78
- // Remove fired events
79
- for (const e of due) {
80
- const index = events.findIndex(ev => ev.id === e.id);
81
- if (index !== -1) events.splice(index, 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
  }
84
  }, 1000);
 
1
+ import { serve } from "bun";
2
+ import { Expo } from "expo-server-sdk";
3
+
4
+ const expo = new Expo();
5
  const events = [];
6
 
7
  const html = `
8
+ <!DOCTYPE html><html><head><title>Timed Event Scheduler</title></head><body>
9
+ <h1>Schedule Timed Event</h1>
10
+ <form onsubmit="submitEvent(event)">
11
+ <label>ID: <input type="text" id="id" required /></label><br />
12
+ <label>Delay (sec): <input type="number" id="delay" required /></label><br />
13
+ <label>Message: <input type="text" id="message" /></label><br />
14
+ <label>Event Type:
15
+ <select id="type">
16
+ <option value="timeout">Timeout</option>
17
+ <option value="interval">Interval</option>
18
+ <option value="webhook">Webhook</option>
19
+ </select>
20
+ </label><br />
21
+ <label>Repeat Every (sec): <input type="number" id="repeat" /></label><br />
22
+ <label>Expo Token(s) (comma-separated): <input type="text" id="tokens" /></label><br />
23
+ <label>Webhook URL: <input type="text" id="webhookUrl" /></label><br />
24
+ <label>Webhook Payload (JSON): <textarea id="payload"></textarea></label><br />
25
+ <button type="submit">Submit</button>
26
+ </form>
27
+ <script>
28
+ async function submitEvent(e) {
29
+ e.preventDefault();
30
+ const id = document.getElementById('id').value;
31
+ const delay = parseInt(document.getElementById('delay').value);
32
+ const message = document.getElementById('message').value;
33
+ const type = document.getElementById('type').value;
34
+ const repeat = parseInt(document.getElementById('repeat').value || 0);
35
+ const tokens = document.getElementById('tokens').value.split(',').map(t => t.trim()).filter(Boolean);
36
+ const webhookUrl = document.getElementById('webhookUrl').value;
37
+ const payloadText = document.getElementById('payload').value;
38
+ let payload = {};
39
+ try {
40
+ if (payloadText) payload = JSON.parse(payloadText);
41
+ } catch {
42
+ alert('Invalid JSON payload');
43
+ return;
44
+ }
45
 
46
+ const res = await fetch('/events', {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ id, delay, message, type, repeat, expoTokens: tokens, webhookUrl, payload })
50
+ });
51
+
52
+ alert(await res.text());
53
+ }
54
+ </script>
55
+ </body></html>
56
  `;
57
 
58
+ serve({
59
  port: 7860,
60
  fetch: async (req) => {
61
  const url = new URL(req.url);
 
64
  return new Response(html, { headers: { "Content-Type": "text/html" } });
65
  }
66
 
67
+ // POST /events - Create Event
68
  if (req.method === "POST" && url.pathname === "/events") {
69
  try {
70
  const body = await req.json();
71
+ const { id, delay, message, type, repeat, expoTokens, webhookUrl, payload } = body;
72
 
73
+ if (!id || !delay || !type) {
74
+ return new Response("Missing 'id', 'delay', or 'type'", { status: 400 });
75
  }
76
 
77
+ if (events.find(e => e.id === id)) {
78
+ return new Response("Event ID already exists", { status: 409 });
79
+ }
80
+
81
+ events.push({
82
+ id,
83
+ timestamp: Date.now() + delay * 1000,
84
+ type,
85
+ repeat: type === "interval" && repeat ? repeat * 1000 : undefined,
86
+ data: {
87
+ message,
88
+ expoTokens: Array.isArray(expoTokens) ? expoTokens : [],
89
+ webhookUrl,
90
+ payload
91
+ }
92
+ });
93
+
94
  return new Response("Event scheduled", { status: 200 });
95
+ } catch (err) {
96
  return new Response("Invalid JSON", { status: 400 });
97
  }
98
  }
99
 
100
+ // DELETE /events/:id - Remove Event
101
+ if (req.method === "DELETE" && url.pathname.startsWith("/events/")) {
102
+ const id = url.pathname.split("/")[2];
103
+ const index = events.findIndex(e => e.id === id);
104
+ if (index === -1) return new Response("Event not found", { status: 404 });
105
+ events.splice(index, 1);
106
+ return new Response("Event deleted", { status: 200 });
107
+ }
108
+
109
  return new Response("Not Found", { status: 404 });
110
  }
111
  });
112
 
113
+ // Event Loop
114
+ setInterval(async () => {
115
  const now = Date.now();
116
  const due = events.filter(e => e.timestamp <= now);
117
+
118
+ for (const e of due) {
119
+ console.log(`⏰ Executing event: ${e.id} (${e.type})`);
120
+
121
+ // Expo Notifications
122
+ if (e.data.expoTokens?.length && e.data.message) {
123
+ const messages = e.data.expoTokens
124
+ .filter(t => Expo.isExpoPushToken(t))
125
+ .map(token => ({
126
+ to: token,
127
+ sound: 'default',
128
+ title: '⏰ Scheduled Event',
129
+ body: e.data.message,
130
+ data: {}
131
+ }));
132
+ for (const chunk of expo.chunkPushNotifications(messages)) {
133
+ try {
134
+ await expo.sendPushNotificationsAsync(chunk);
135
+ } catch (err) {
136
+ console.error("🔥 Expo error:", err);
137
+ }
138
+ }
139
+ }
140
+
141
+ // Webhook Trigger
142
+ if (e.type === "webhook" && e.data.webhookUrl) {
143
+ try {
144
+ await fetch(e.data.webhookUrl, {
145
+ method: "POST",
146
+ headers: { "Content-Type": "application/json" },
147
+ body: JSON.stringify(e.data.payload || {})
148
+ });
149
+ console.log(`📡 Webhook fired for ${e.id}`);
150
+ } catch (err) {
151
+ console.error(`❌ Webhook failed for ${e.id}`, err);
152
+ }
153
+ }
154
+
155
+ // Repeat or remove
156
+ if (e.type === "interval" && e.repeat) {
157
+ e.timestamp = now + e.repeat;
158
+ } else {
159
+ const i = events.findIndex(ev => ev.id === e.id);
160
+ if (i !== -1) events.splice(i, 1);
161
  }
162
  }
163
  }, 1000);