@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.
- package/CHANGELOG.md +112 -0
- package/README.md +625 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +86 -0
- package/examples/basic/components/OfflineIndicator.tsx +103 -0
- package/examples/basic/components/PresenceBar.tsx +77 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +80 -0
- package/package.json +101 -0
- package/packages/analytics/README.md +309 -0
- package/packages/analytics/build.ts +35 -0
- package/packages/analytics/package.json +50 -0
- package/packages/analytics/src/click-tracker.ts +368 -0
- package/packages/analytics/src/context-bridge.ts +319 -0
- package/packages/analytics/src/data-layer.ts +302 -0
- package/packages/analytics/src/gtm-loader.ts +239 -0
- package/packages/analytics/src/index.ts +230 -0
- package/packages/analytics/src/merkle-tree.ts +489 -0
- package/packages/analytics/src/provider.tsx +300 -0
- package/packages/analytics/src/types.ts +320 -0
- package/packages/analytics/src/use-analytics.ts +296 -0
- package/packages/analytics/tsconfig.json +19 -0
- package/packages/benchmarks/src/benchmark.test.ts +691 -0
- package/packages/cli/dist/index.js +61899 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +682 -0
- package/packages/cli/src/commands/build.ts +890 -0
- package/packages/cli/src/commands/dev.ts +473 -0
- package/packages/cli/src/commands/init.ts +409 -0
- package/packages/cli/src/commands/start.ts +297 -0
- package/packages/cli/src/index.ts +105 -0
- package/packages/directives/src/use-aeon.ts +272 -0
- package/packages/mcp-server/package.json +51 -0
- package/packages/mcp-server/src/index.ts +178 -0
- package/packages/mcp-server/src/resources.ts +346 -0
- package/packages/mcp-server/src/tools/index.ts +36 -0
- package/packages/mcp-server/src/tools/navigation.ts +545 -0
- package/packages/mcp-server/tsconfig.json +21 -0
- package/packages/react/package.json +40 -0
- package/packages/react/src/Link.tsx +388 -0
- package/packages/react/src/components/InstallPrompt.tsx +286 -0
- package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
- package/packages/react/src/components/PushNotifications.tsx +453 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
- package/packages/react/src/hooks/useConflicts.ts +277 -0
- package/packages/react/src/hooks/useNetworkState.ts +209 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
- package/packages/react/src/hooks/useServiceWorker.ts +278 -0
- package/packages/react/src/hooks.ts +195 -0
- package/packages/react/src/index.ts +151 -0
- package/packages/react/src/provider.tsx +467 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/runtime/README.md +399 -0
- package/packages/runtime/build.ts +48 -0
- package/packages/runtime/package.json +71 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +465 -0
- package/packages/runtime/src/benchmark.ts +171 -0
- package/packages/runtime/src/cache.ts +479 -0
- package/packages/runtime/src/durable-object.ts +1341 -0
- package/packages/runtime/src/index.ts +360 -0
- package/packages/runtime/src/navigation.test.ts +421 -0
- package/packages/runtime/src/navigation.ts +422 -0
- package/packages/runtime/src/nextjs-adapter.ts +272 -0
- package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
- package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
- package/packages/runtime/src/offline/encryption.test.ts +412 -0
- package/packages/runtime/src/offline/encryption.ts +397 -0
- package/packages/runtime/src/offline/types.ts +465 -0
- package/packages/runtime/src/predictor.ts +371 -0
- package/packages/runtime/src/registry.ts +351 -0
- package/packages/runtime/src/router/context-extractor.ts +661 -0
- package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
- package/packages/runtime/src/router/esi-control.ts +541 -0
- package/packages/runtime/src/router/esi-cyrano.ts +779 -0
- package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
- package/packages/runtime/src/router/esi-react.tsx +1065 -0
- package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
- package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
- package/packages/runtime/src/router/esi-translate.ts +503 -0
- package/packages/runtime/src/router/esi.ts +666 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
- package/packages/runtime/src/router/index.ts +298 -0
- package/packages/runtime/src/router/merkle-capability.ts +473 -0
- package/packages/runtime/src/router/speculation.ts +451 -0
- package/packages/runtime/src/router/types.ts +630 -0
- package/packages/runtime/src/router.test.ts +470 -0
- package/packages/runtime/src/router.ts +302 -0
- package/packages/runtime/src/server.ts +481 -0
- package/packages/runtime/src/service-worker-push.ts +319 -0
- package/packages/runtime/src/service-worker.ts +553 -0
- package/packages/runtime/src/skeleton-hydrate.ts +237 -0
- package/packages/runtime/src/speculation.test.ts +389 -0
- package/packages/runtime/src/speculation.ts +486 -0
- package/packages/runtime/src/storage.test.ts +1297 -0
- package/packages/runtime/src/storage.ts +1048 -0
- package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
- package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
- package/packages/runtime/src/sync/coordinator.test.ts +608 -0
- package/packages/runtime/src/sync/coordinator.ts +596 -0
- package/packages/runtime/src/tree-compiler.ts +295 -0
- package/packages/runtime/src/types.ts +728 -0
- package/packages/runtime/src/worker.ts +327 -0
- package/packages/runtime/tsconfig.json +20 -0
- package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
- package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
- package/packages/runtime/wasm/package.json +21 -0
- package/packages/runtime/wrangler.toml +41 -0
- package/packages/runtime-wasm/Cargo.lock +436 -0
- package/packages/runtime-wasm/Cargo.toml +29 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
- package/packages/runtime-wasm/pkg/package.json +21 -0
- package/packages/runtime-wasm/src/hydrate.rs +352 -0
- package/packages/runtime-wasm/src/lib.rs +191 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/packages/runtime-wasm/src/skeleton.rs +430 -0
- 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;
|