@affectively/aeon-pages 1.3.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Aeon Pages Service Worker Push Handler
3
+ *
4
+ * Optional push notification handler for service workers.
5
+ * Only loaded when push feature is enabled.
6
+ *
7
+ * Features:
8
+ * - Push event handling with notification display
9
+ * - Notification click handling with navigation
10
+ * - Background sync integration
11
+ * - Badge updates
12
+ */
13
+
14
+ // ============================================================================
15
+ // Types
16
+ // ============================================================================
17
+
18
+ export interface PushNotificationData {
19
+ title: string;
20
+ body?: string;
21
+ icon?: string;
22
+ badge?: string;
23
+ tag?: string;
24
+ data?: {
25
+ url?: string;
26
+ action?: string;
27
+ [key: string]: unknown;
28
+ };
29
+ requireInteraction?: boolean;
30
+ vibrate?: number[];
31
+ actions?: Array<{
32
+ action: string;
33
+ title: string;
34
+ icon?: string;
35
+ }>;
36
+ }
37
+
38
+ export interface PushHandlerConfig {
39
+ defaultIcon?: string;
40
+ defaultBadge?: string;
41
+ defaultVibrate?: number[];
42
+ onNotificationClick?: (
43
+ data: PushNotificationData['data'],
44
+ ) => string | undefined;
45
+ }
46
+
47
+ // ============================================================================
48
+ // Push Event Handler
49
+ // ============================================================================
50
+
51
+ /**
52
+ * Handle incoming push event
53
+ */
54
+ export function handlePush(
55
+ event: PushEvent,
56
+ config: PushHandlerConfig = {},
57
+ ): void {
58
+ if (!event.data) {
59
+ console.warn('[AeonSW] Push event received with no data');
60
+ return;
61
+ }
62
+
63
+ let data: PushNotificationData;
64
+
65
+ try {
66
+ data = event.data.json() as PushNotificationData;
67
+ } catch {
68
+ // If not JSON, treat as plain text
69
+ data = {
70
+ title: 'Notification',
71
+ body: event.data.text(),
72
+ };
73
+ }
74
+
75
+ // Extended notification options with vibrate (not in all type definitions)
76
+ const notificationOptions = {
77
+ body: data.body,
78
+ icon: data.icon || config.defaultIcon,
79
+ badge: data.badge || config.defaultBadge,
80
+ tag: data.tag || 'aeon-notification',
81
+ data: data.data,
82
+ requireInteraction: data.requireInteraction || false,
83
+ vibrate: data.vibrate || config.defaultVibrate || [200, 100, 200],
84
+ actions: data.actions,
85
+ } as NotificationOptions;
86
+
87
+ event.waitUntil(
88
+ self.registration.showNotification(data.title, notificationOptions),
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Handle notification click event
94
+ */
95
+ export function handleNotificationClick(
96
+ event: NotificationEvent,
97
+ config: PushHandlerConfig = {},
98
+ ): void {
99
+ event.notification.close();
100
+
101
+ const data = event.notification.data as PushNotificationData['data'];
102
+ let targetUrl = '/';
103
+
104
+ // Check for action-specific URL
105
+ if (event.action && data?.action) {
106
+ // Handle specific action
107
+ targetUrl = data.action;
108
+ } else if (data?.url) {
109
+ targetUrl = data.url;
110
+ } else if (config.onNotificationClick) {
111
+ const customUrl = config.onNotificationClick(data);
112
+ if (customUrl) {
113
+ targetUrl = customUrl;
114
+ }
115
+ }
116
+
117
+ event.waitUntil(
118
+ clients
119
+ .matchAll({ type: 'window', includeUncontrolled: true })
120
+ .then((clientList) => {
121
+ // Check if there's already a window open
122
+ for (const client of clientList) {
123
+ if ('focus' in client && client.url.includes(self.location.origin)) {
124
+ return client.focus().then((focusedClient) => {
125
+ if ('navigate' in focusedClient) {
126
+ return (focusedClient as WindowClient).navigate(targetUrl);
127
+ }
128
+ return Promise.resolve(); // Ensure a return even if navigate is not available
129
+ });
130
+ }
131
+ }
132
+
133
+ // Open a new window if no existing window found
134
+ if (clients.openWindow) {
135
+ return clients.openWindow(targetUrl);
136
+ }
137
+ return Promise.resolve(); // Ensure a return if openWindow is not available
138
+ }),
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Handle notification close event
144
+ */
145
+ export function handleNotificationClose(event: NotificationEvent): void {
146
+ // Analytics or cleanup can be done here
147
+ console.debug('[AeonSW] Notification closed:', event.notification.tag);
148
+ }
149
+
150
+ // ============================================================================
151
+ // Background Sync Handler
152
+ // ============================================================================
153
+
154
+ /**
155
+ * Handle background sync event for offline queue
156
+ */
157
+ export function handleSync(event: ExtendableEvent, tag: string): void {
158
+ if (tag === 'aeon-offline-sync') {
159
+ event.waitUntil(syncOfflineQueue());
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Sync offline queue with server
165
+ */
166
+ async function syncOfflineQueue(): Promise<void> {
167
+ // This would be implemented to read from IndexedDB and sync
168
+ // For now, we'll post a message to any listening clients
169
+ const clientList = await clients.matchAll({ type: 'window' });
170
+
171
+ for (const client of clientList) {
172
+ client.postMessage({
173
+ type: 'SYNC_OFFLINE_QUEUE',
174
+ timestamp: Date.now(),
175
+ });
176
+ }
177
+ }
178
+
179
+ // ============================================================================
180
+ // Message Handler
181
+ // ============================================================================
182
+
183
+ export interface ServiceWorkerMessage {
184
+ type: string;
185
+ payload?: unknown;
186
+ }
187
+
188
+ /**
189
+ * Handle messages from main thread
190
+ */
191
+ export function handleMessage(
192
+ event: ExtendableMessageEvent,
193
+ handlers: Record<string, (payload: unknown) => Promise<unknown> | unknown>,
194
+ ): void {
195
+ const message = event.data as ServiceWorkerMessage;
196
+
197
+ if (!message || !message.type) {
198
+ return;
199
+ }
200
+
201
+ const handler = handlers[message.type];
202
+ if (handler) {
203
+ const result = handler(message.payload);
204
+
205
+ // If handler returns a promise, send response when resolved
206
+ if (result instanceof Promise) {
207
+ event.waitUntil(
208
+ result.then((response) => {
209
+ if (event.source && 'postMessage' in event.source) {
210
+ (event.source as Client).postMessage({
211
+ type: `${message.type}_RESPONSE`,
212
+ payload: response,
213
+ });
214
+ }
215
+ }),
216
+ );
217
+ }
218
+ }
219
+ }
220
+
221
+ // ============================================================================
222
+ // Service Worker Registration Helpers
223
+ // ============================================================================
224
+
225
+ /**
226
+ * Register push event handlers in service worker
227
+ */
228
+ export function registerPushHandlers(
229
+ sw: ServiceWorkerGlobalScope,
230
+ config: PushHandlerConfig = {},
231
+ ): void {
232
+ sw.addEventListener('push', (event) => {
233
+ handlePush(event, config);
234
+ });
235
+
236
+ sw.addEventListener('notificationclick', (event) => {
237
+ handleNotificationClick(event, config);
238
+ });
239
+
240
+ sw.addEventListener('notificationclose', (event) => {
241
+ handleNotificationClose(event);
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Register sync handlers in service worker
247
+ */
248
+ export function registerSyncHandlers(sw: ServiceWorkerGlobalScope): void {
249
+ sw.addEventListener('sync', (event: Event) => {
250
+ const syncEvent = event as ExtendableEvent & { tag: string };
251
+ handleSync(syncEvent, syncEvent.tag);
252
+ });
253
+ }
254
+
255
+ /**
256
+ * Register message handlers in service worker
257
+ */
258
+ export function registerMessageHandlers(
259
+ sw: ServiceWorkerGlobalScope,
260
+ handlers: Record<string, (payload: unknown) => Promise<unknown> | unknown>,
261
+ ): void {
262
+ sw.addEventListener('message', (event) => {
263
+ handleMessage(event, handlers);
264
+ });
265
+ }
266
+
267
+ // ============================================================================
268
+ // Utility Functions
269
+ // ============================================================================
270
+
271
+ /**
272
+ * Convert VAPID public key from base64url to Uint8Array
273
+ */
274
+ export function urlBase64ToUint8Array(base64String: string): Uint8Array {
275
+ const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
276
+ const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
277
+
278
+ const rawData = atob(base64);
279
+ const outputArray = new Uint8Array(rawData.length);
280
+
281
+ for (let i = 0; i < rawData.length; ++i) {
282
+ outputArray[i] = rawData.charCodeAt(i);
283
+ }
284
+ return outputArray;
285
+ }
286
+
287
+ /**
288
+ * Serialize push subscription for sending to server
289
+ */
290
+ export function serializePushSubscription(subscription: PushSubscription): {
291
+ endpoint: string;
292
+ keys: { p256dh: string; auth: string };
293
+ } {
294
+ const p256dh = subscription.getKey('p256dh');
295
+ const auth = subscription.getKey('auth');
296
+
297
+ return {
298
+ endpoint: subscription.endpoint,
299
+ keys: {
300
+ p256dh: p256dh
301
+ ? btoa(
302
+ String.fromCharCode.apply(null, Array.from(new Uint8Array(p256dh))),
303
+ )
304
+ : '',
305
+ auth: auth
306
+ ? btoa(
307
+ String.fromCharCode.apply(null, Array.from(new Uint8Array(auth))),
308
+ )
309
+ : '',
310
+ },
311
+ };
312
+ }
313
+
314
+ // ============================================================================
315
+ // Type declarations for ServiceWorker context
316
+ // ============================================================================
317
+
318
+ declare const self: ServiceWorkerGlobalScope;
319
+ declare const clients: Clients;