File size: 5,366 Bytes
fc2332b
 
 
 
2d34f28
7fe989f
2d34f28
fc2332b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
be81bd0
fc2332b
 
 
 
 
 
 
 
 
 
2d34f28
072db80
fc2332b
2d34f28
 
 
0acab33
2d34f28
 
 
0acab33
fc2332b
2d34f28
0acab33
2d34f28
fc2332b
0acab33
fc2332b
 
0acab33
 
fc2332b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d34f28
fc2332b
2d34f28
 
 
0acab33
fc2332b
 
 
 
 
 
 
 
 
2d34f28
76320ac
2d34f28
d3bdf51
fc2332b
 
2d34f28
 
fc2332b
 
 
 
 
 
f5e294a
fc2332b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5e294a
fc2332b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d34f28
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { serve } from "bun";
import { Expo } from "expo-server-sdk";

const expo = new Expo();
const events = [];

const html = `
<!DOCTYPE html><html><head><title>Timed Event Scheduler</title></head><body>
<h1>Schedule Timed Event</h1>
<form onsubmit="submitEvent(event)">
  <label>ID: <input type="text" id="id" required /></label><br />
  <label>Delay (sec): <input type="number" id="delay" required /></label><br />
  <label>Message: <input type="text" id="message" /></label><br />
  <label>Event Type:
    <select id="type">
      <option value="timeout">Timeout</option>
      <option value="interval">Interval</option>
      <option value="webhook">Webhook</option>
    </select>
  </label><br />
  <label>Repeat Every (sec): <input type="number" id="repeat" /></label><br />
  <label>Expo Token(s) (comma-separated): <input type="text" id="tokens" /></label><br />
  <label>Webhook URL: <input type="text" id="webhookUrl" /></label><br />
  <label>Webhook Payload (JSON): <textarea id="payload"></textarea></label><br />
  <button type="submit">Submit</button>
</form>
<script>
async function submitEvent(e) {
  e.preventDefault();
  const id = document.getElementById('id').value;
  const delay = parseInt(document.getElementById('delay').value);
  const message = document.getElementById('message').value;
  const type = document.getElementById('type').value;
  const repeat = parseInt(document.getElementById('repeat').value || 0);
  const tokens = document.getElementById('tokens').value.split(',').map(t => t.trim()).filter(Boolean);
  const webhookUrl = document.getElementById('webhookUrl').value;
  const payloadText = document.getElementById('payload').value;
  let payload = {};
  try {
    if (payloadText) payload = JSON.parse(payloadText);
  } catch {
    alert('Invalid JSON payload');
    return;
  }

  const res = await fetch('/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id, delay, message, type, repeat, expoTokens: tokens, webhookUrl, payload })
  });

  alert(await res.text());
}
</script>
</body></html>
`;

serve({
  port: 7860,
  fetch: async (req) => {
    const url = new URL(req.url);

    if (req.method === "GET" && url.pathname === "/") {
      return new Response(html, { headers: { "Content-Type": "text/html" } });
    }

    // POST /events - Create Event
    if (req.method === "POST" && url.pathname === "/events") {
      try {
        const body = await req.json();
        const { id, delay, message, type, repeat, expoTokens, webhookUrl, payload } = body;

        if (!id || !delay || !type) {
          return new Response("Missing 'id', 'delay', or 'type'", { status: 400 });
        }

        if (events.find(e => e.id === id)) {
          return new Response("Event ID already exists", { status: 409 });
        }

        events.push({
          id,
          timestamp: Date.now() + delay * 1000,
          type,
          repeat: type === "interval" && repeat ? repeat * 1000 : undefined,
          data: {
            message,
            expoTokens: Array.isArray(expoTokens) ? expoTokens : [],
            webhookUrl,
            payload
          }
        });

        return new Response("Event scheduled", { status: 200 });
      } catch (err) {
        return new Response("Invalid JSON", { status: 400 });
      }
    }

    // DELETE /events/:id - Remove Event
    if (req.method === "DELETE" && url.pathname.startsWith("/events/")) {
      const id = url.pathname.split("/")[2];
      const index = events.findIndex(e => e.id === id);
      if (index === -1) return new Response("Event not found", { status: 404 });
      events.splice(index, 1);
      return new Response("Event deleted", { status: 200 });
    }

    return new Response("Not Found", { status: 404 });
  }
});

// Event Loop
setInterval(async () => {
  const now = Date.now();
  const due = events.filter(e => e.timestamp <= now);

  for (const e of due) {
    console.log(`⏰ Executing event: ${e.id} (${e.type})`);

    // Expo Notifications
    if (e.data.expoTokens?.length && e.data.message) {
      console.log("found a notifications events")
      const messages = e.data.expoTokens
        .filter(t => Expo.isExpoPushToken(t))
        .map(token => ({
          to: token,
          sound: 'default',
          title: '⏰ Scheduled Event',
          body: e.data.message,
          data: {}
        }));
      for (const chunk of expo.chunkPushNotifications(messages)) {
        try {
          await expo.sendPushNotificationsAsync(chunk);
        } catch (err) {
          console.error("🔥 Expo error:", err);
        }
      }
    }

    // Webhook Trigger
    if (e.type === "webhook" && e.data.webhookUrl) {
      console.log("found a webhook event")
      try {
        await fetch(e.data.webhookUrl, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(e.data.payload || {})
        });
        console.log(`📡 Webhook fired for ${e.id}`);
      } catch (err) {
        console.error(`❌ Webhook failed for ${e.id}`, err);
      }
    }

    // Repeat or remove
    if (e.type === "interval" && e.repeat) {
      e.timestamp = now + e.repeat;
    } else {
      const i = events.findIndex(ev => ev.id === e.id);
      if (i !== -1) events.splice(i, 1);
    }
  }
}, 1000);