@absolutejs/absolute 0.20.0-beta.3 → 0.20.0-beta.31
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 +167 -0
- package/dist/angular/browser.js +15 -1
- package/dist/angular/browser.js.map +3 -3
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/angular/index.js +341 -22
- package/dist/angular/index.js.map +8 -5
- package/dist/angular/server.js +341 -22
- package/dist/angular/server.js.map +8 -5
- package/dist/build.js +2058 -1105
- package/dist/build.js.map +17 -13
- package/dist/cli/index.js +4085 -1139
- package/dist/dev/client/cssUtils.ts +16 -2
- package/dist/dev/client/handlers/rebuild.ts +11 -1
- package/dist/dev/client/hmrClient.ts +32 -3
- package/dist/dev/client/hmrTiming.ts +14 -7
- package/dist/dev/client/syncDevtools.ts +237 -0
- package/dist/index.js +2483 -1367
- package/dist/index.js.map +27 -22
- package/dist/mobile/browser.js +123 -1
- package/dist/mobile/browser.js.map +6 -4
- package/dist/mobile/index.js +2948 -328
- package/dist/mobile/index.js.map +27 -13
- package/dist/mobile/remoteMacAgentEntry.js +29 -0
- package/dist/mobile/shellAuth.js +36 -0
- package/dist/mobile/shellBootstrap.js +806 -0
- package/dist/mobile/shellSync.js +173 -0
- package/dist/src/angular/pageHandler.d.ts +3 -0
- package/dist/src/build/pwa.d.ts +16 -0
- package/dist/src/cli/config/server.d.ts +1 -1
- package/dist/src/core/devBuild.d.ts +1 -0
- package/dist/src/core/pageHandlers.d.ts +11 -2
- package/dist/src/core/prepare.d.ts +24 -0
- package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
- package/dist/src/mobile/androidTestReport.d.ts +14 -0
- package/dist/src/mobile/androidUpgradeConformance.d.ts +47 -0
- package/dist/src/mobile/androidWebView.d.ts +1 -0
- package/dist/src/mobile/browser.d.ts +1 -0
- package/dist/src/mobile/buildPipeline.d.ts +1 -0
- package/dist/src/mobile/capacitorBundle.d.ts +22 -1
- package/dist/src/mobile/client.d.ts +4 -0
- package/dist/src/mobile/config.d.ts +1 -0
- package/dist/src/mobile/devDeviceAdapter.d.ts +3 -0
- package/dist/src/mobile/deviceCapabilities.d.ts +46 -0
- package/dist/src/mobile/index.d.ts +8 -0
- package/dist/src/mobile/iosTestReport.d.ts +23 -0
- package/dist/src/mobile/nativeAuth.d.ts +17 -0
- package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
- package/dist/src/mobile/nativeDeviceCapabilities.d.ts +6 -0
- package/dist/src/mobile/nativeTestReport.d.ts +85 -0
- package/dist/src/mobile/releaseArtifact.d.ts +2 -0
- package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
- package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
- package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
- package/dist/src/mobile/remoteMacWire.d.ts +2 -0
- package/dist/src/mobile/shellAuth.d.ts +15 -0
- package/dist/src/mobile/shellBootstrap.d.ts +22 -1
- package/dist/src/mobile/shellHttp.d.ts +2 -0
- package/dist/src/mobile/shellPush.d.ts +11 -0
- package/dist/src/mobile/shellSync.d.ts +47 -0
- package/dist/src/mobile/staticDocument.d.ts +5 -0
- package/dist/src/mobile/syncRemediation.d.ts +10 -0
- package/dist/src/mobile/syncSchema.d.ts +9 -0
- package/dist/src/mobile/transport.d.ts +16 -1
- package/dist/src/plugins/imageOptimizer.d.ts +1 -1
- package/dist/src/svelte/pageHandler.d.ts +3 -0
- package/dist/src/utils/imageProcessing.d.ts +3 -0
- package/dist/src/utils/loadConfig.d.ts +1 -0
- package/dist/src/vue/pageHandler.d.ts +3 -0
- package/dist/svelte/index.js +312 -23
- package/dist/svelte/index.js.map +7 -4
- package/dist/svelte/server.js +307 -18
- package/dist/svelte/server.js.map +7 -4
- package/dist/types/build.d.ts +29 -0
- package/dist/vue/index.js +312 -23
- package/dist/vue/index.js.map +7 -4
- package/dist/vue/server.js +307 -18
- package/dist/vue/server.js.map +7 -4
- package/package.json +38 -10
|
@@ -151,7 +151,8 @@ const findManifestHref = (
|
|
|
151
151
|
|
|
152
152
|
const replaceStylesheetLink = (
|
|
153
153
|
existingLink: HTMLLinkElement,
|
|
154
|
-
newHref: string
|
|
154
|
+
newHref: string,
|
|
155
|
+
attempt = 0
|
|
155
156
|
) => {
|
|
156
157
|
const replacement = existingLink.cloneNode(true);
|
|
157
158
|
if (!(replacement instanceof HTMLLinkElement)) {
|
|
@@ -167,7 +168,20 @@ const replaceStylesheetLink = (
|
|
|
167
168
|
resolve(applied);
|
|
168
169
|
};
|
|
169
170
|
replacement.onload = () => finish(true);
|
|
170
|
-
replacement.onerror = () =>
|
|
171
|
+
replacement.onerror = () => {
|
|
172
|
+
if (attempt >= 1) {
|
|
173
|
+
finish(false);
|
|
174
|
+
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
replacement.remove();
|
|
178
|
+
settled = true;
|
|
179
|
+
void replaceStylesheetLink(
|
|
180
|
+
existingLink,
|
|
181
|
+
`${newHref}${newHref.includes('?') ? '&' : '?'}retry=${Date.now()}`,
|
|
182
|
+
attempt + 1
|
|
183
|
+
).then(resolve);
|
|
184
|
+
};
|
|
171
185
|
replacement.href = newHref;
|
|
172
186
|
existingLink.after(replacement);
|
|
173
187
|
setTimeout(() => {
|
|
@@ -7,12 +7,22 @@ import {
|
|
|
7
7
|
isRuntimeErrorOverlay,
|
|
8
8
|
showErrorOverlay
|
|
9
9
|
} from '../errorOverlay';
|
|
10
|
+
import { detectCurrentFramework } from '../frameworkDetect';
|
|
10
11
|
import { sendAbsoluteHmrTiming } from '../hmrTiming';
|
|
11
12
|
|
|
12
13
|
export const handleFullReload = (message?: {
|
|
13
|
-
data: { serverDuration?: number };
|
|
14
|
+
data: { affectedFrameworks?: string[]; serverDuration?: number };
|
|
14
15
|
timestamp?: number;
|
|
15
16
|
}) => {
|
|
17
|
+
const affectedFrameworks = message?.data.affectedFrameworks;
|
|
18
|
+
const currentFramework = detectCurrentFramework();
|
|
19
|
+
if (
|
|
20
|
+
affectedFrameworks?.length &&
|
|
21
|
+
currentFramework &&
|
|
22
|
+
!affectedFrameworks.includes(currentFramework)
|
|
23
|
+
) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
16
26
|
const clientStart = performance.now();
|
|
17
27
|
sendAbsoluteHmrTiming({
|
|
18
28
|
clientStart,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
sendAbsoluteHmrTiming
|
|
19
19
|
} from './hmrTiming';
|
|
20
20
|
import { hideErrorOverlay, showErrorOverlay } from './errorOverlay';
|
|
21
|
+
import { installAbsoluteNativeSyncDevtools } from './syncDevtools';
|
|
21
22
|
import {
|
|
22
23
|
dispatchAngularComponentRemount,
|
|
23
24
|
dispatchAngularComponentUpdate
|
|
@@ -45,6 +46,33 @@ const isStringRecord = (value: unknown): value is Record<string, string> =>
|
|
|
45
46
|
Object.values(value).every((entry) => typeof entry === 'string');
|
|
46
47
|
|
|
47
48
|
restoreAbsoluteHmrApply();
|
|
49
|
+
const nativeDeviceAdapterPath = '/__absolute/native-device-adapter.js';
|
|
50
|
+
const nativeDeviceAdapterReady =
|
|
51
|
+
absoluteHmrClientTarget() === 'web'
|
|
52
|
+
? Promise.resolve()
|
|
53
|
+
: import(nativeDeviceAdapterPath).then(() => undefined);
|
|
54
|
+
Reflect.set(
|
|
55
|
+
globalThis,
|
|
56
|
+
'__ABS_NATIVE_DEVICES_READY__',
|
|
57
|
+
nativeDeviceAdapterReady
|
|
58
|
+
);
|
|
59
|
+
Reflect.set(globalThis, '__ABS_NATIVE_DEVICES_READY_STATE__', 'loading');
|
|
60
|
+
void nativeDeviceAdapterReady.then(
|
|
61
|
+
() => {
|
|
62
|
+
Reflect.set(globalThis, '__ABS_NATIVE_DEVICES_READY_STATE__', 'ready');
|
|
63
|
+
},
|
|
64
|
+
(error) => {
|
|
65
|
+
Reflect.set(globalThis, '__ABS_NATIVE_DEVICES_READY_STATE__', 'failed');
|
|
66
|
+
console.error(
|
|
67
|
+
'[Absolute Mobile] Native device adapter failed to load:',
|
|
68
|
+
error
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
const removeNativeSyncDevtools =
|
|
73
|
+
absoluteHmrClientTarget() === 'web'
|
|
74
|
+
? () => undefined
|
|
75
|
+
: installAbsoluteNativeSyncDevtools();
|
|
48
76
|
|
|
49
77
|
/* Lightweight "server disconnected" banner. When the dev server is
|
|
50
78
|
* genuinely down (process restarting or crashed) the browser would
|
|
@@ -165,15 +193,15 @@ type HMRMessage = {
|
|
|
165
193
|
|
|
166
194
|
const handleStylesheetUpdate = (message: HMRMessage) => {
|
|
167
195
|
const clientStart = performance.now();
|
|
168
|
-
void reloadCSSStylesheets(message.data.manifest ?? {}).then((applied) =>
|
|
196
|
+
void reloadCSSStylesheets(message.data.manifest ?? {}).then((applied) =>
|
|
169
197
|
sendAbsoluteHmrTiming({
|
|
170
198
|
clientStart,
|
|
171
199
|
kind: 'css',
|
|
172
200
|
outcome: applied ? 'applied' : 'failed',
|
|
173
201
|
serverMs: message.data.serverDuration,
|
|
174
202
|
updateId: message.timestamp
|
|
175
|
-
})
|
|
176
|
-
|
|
203
|
+
})
|
|
204
|
+
);
|
|
177
205
|
};
|
|
178
206
|
|
|
179
207
|
const handleHMRMessage = (message: HMRMessage) => {
|
|
@@ -413,6 +441,7 @@ if (!(window.__HMR_WS__ && window.__HMR_WS__.readyState === WebSocket.OPEN)) {
|
|
|
413
441
|
};
|
|
414
442
|
|
|
415
443
|
window.addEventListener('beforeunload', () => {
|
|
444
|
+
removeNativeSyncDevtools();
|
|
416
445
|
if (hmrState.isHMRUpdating) {
|
|
417
446
|
if (hmrState.pingInterval) clearInterval(hmrState.pingInterval);
|
|
418
447
|
if (hmrState.reconnectTimeout)
|
|
@@ -47,14 +47,14 @@ const getSessionStorage = () => {
|
|
|
47
47
|
return storage;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
const
|
|
50
|
+
const persistHmrApplies = (timings: HmrApply[]) => {
|
|
51
51
|
try {
|
|
52
52
|
const storage = getSessionStorage();
|
|
53
53
|
const setItem = storage && Reflect.get(storage, 'setItem');
|
|
54
54
|
if (typeof setItem !== 'function') return;
|
|
55
55
|
Reflect.apply(setItem, storage, [
|
|
56
56
|
HMR_APPLY_STORAGE_KEY,
|
|
57
|
-
JSON.stringify(
|
|
57
|
+
JSON.stringify(timings)
|
|
58
58
|
]);
|
|
59
59
|
} catch {
|
|
60
60
|
// Storage may be unavailable in sandboxed or privacy-restricted documents.
|
|
@@ -100,10 +100,17 @@ export const restoreAbsoluteHmrApply = () => {
|
|
|
100
100
|
HMR_APPLY_STORAGE_KEY
|
|
101
101
|
]);
|
|
102
102
|
if (typeof serialized !== 'string') return;
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
const stored: unknown = JSON.parse(serialized);
|
|
104
|
+
let timings: HmrApply[] = [];
|
|
105
|
+
if (Array.isArray(stored)) {
|
|
106
|
+
timings = stored
|
|
107
|
+
.filter(isHmrApply)
|
|
108
|
+
.slice(-MAX_RETAINED_HMR_APPLIES);
|
|
109
|
+
} else if (isHmrApply(stored)) timings = [stored];
|
|
110
|
+
const last = timings.at(-1);
|
|
111
|
+
if (!last) return;
|
|
112
|
+
window.__ABS_HMR_LAST_APPLY__ = last;
|
|
113
|
+
window.__ABS_HMR_APPLIES__ = timings;
|
|
107
114
|
} catch {
|
|
108
115
|
// A malformed or inaccessible entry should never affect HMR startup.
|
|
109
116
|
}
|
|
@@ -135,7 +142,7 @@ export const sendAbsoluteHmrTiming = (options: SendHmrTimingOptions) => {
|
|
|
135
142
|
if (applies.length > MAX_RETAINED_HMR_APPLIES) {
|
|
136
143
|
applies.splice(0, applies.length - MAX_RETAINED_HMR_APPLIES);
|
|
137
144
|
}
|
|
138
|
-
|
|
145
|
+
persistHmrApplies(applies);
|
|
139
146
|
if (!window.__HMR_WS__) return;
|
|
140
147
|
window.__HMR_WS__.send(JSON.stringify({ ...timing, type: 'hmr-timing' }));
|
|
141
148
|
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type {} from '../../types/globals';
|
|
2
|
+
import {
|
|
3
|
+
discardSyncRuntimeDeadLetter,
|
|
4
|
+
inspectSyncRuntime,
|
|
5
|
+
rebaseSyncRuntimeDeadLetter,
|
|
6
|
+
retrySyncRuntimeDeadLetter,
|
|
7
|
+
type SyncRuntimeInspection
|
|
8
|
+
} from '@absolutejs/sync/client/runtime';
|
|
9
|
+
|
|
10
|
+
export type SyncDevtoolsBridge = {
|
|
11
|
+
discard: (operationId: string) => Promise<void>;
|
|
12
|
+
inspect: () => Promise<SyncRuntimeInspection>;
|
|
13
|
+
rebase: (operationId: string, args: unknown) => Promise<string>;
|
|
14
|
+
retry: (operationId: string) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DEVTOOLS_ID = 'absolutejs-sync-devtools';
|
|
18
|
+
const REFRESH_INTERVAL_MS = 1_500;
|
|
19
|
+
const bridge: SyncDevtoolsBridge = {
|
|
20
|
+
discard: discardSyncRuntimeDeadLetter,
|
|
21
|
+
inspect: inspectSyncRuntime,
|
|
22
|
+
rebase: rebaseSyncRuntimeDeadLetter,
|
|
23
|
+
retry: retrySyncRuntimeDeadLetter
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const time = (value: number | undefined) =>
|
|
27
|
+
value === undefined ? '—' : new Date(value).toLocaleTimeString();
|
|
28
|
+
|
|
29
|
+
const styles = `
|
|
30
|
+
:host { all: initial; color-scheme: light dark; }
|
|
31
|
+
button { font: inherit; }
|
|
32
|
+
.trigger { position:fixed;right:max(12px,env(safe-area-inset-right));bottom:max(12px,env(safe-area-inset-bottom));z-index:2147483645;border:0;border-radius:999px;padding:9px 13px;background:#111827;color:#fff;box-shadow:0 4px 18px #0006;font:600 12px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
33
|
+
.trigger[data-alert=true] { background:#b91c1c; }
|
|
34
|
+
.panel { position:fixed;inset:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(56px,env(safe-area-inset-bottom)) auto;z-index:2147483645;width:min(420px,calc(100vw - 24px));max-height:calc(100vh - 80px);overflow:auto;border:1px solid #64748b66;border-radius:14px;background:#fffffff2;color:#111827;box-shadow:0 16px 48px #0008;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;backdrop-filter:blur(12px); }
|
|
35
|
+
[hidden] { display:none!important; }
|
|
36
|
+
header { position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid #64748b44;background:inherit; }
|
|
37
|
+
h2 { margin:0;font-size:14px; }
|
|
38
|
+
.close,.action { border:1px solid #64748b66;border-radius:7px;background:transparent;color:inherit;padding:5px 8px; }
|
|
39
|
+
.body { padding:12px 14px; }
|
|
40
|
+
.metrics { display:grid;grid-template-columns:repeat(2,1fr);gap:7px;margin-bottom:12px; }
|
|
41
|
+
.metric { padding:8px;border-radius:8px;background:#64748b18; }
|
|
42
|
+
.metric strong { display:block;font-size:16px; }
|
|
43
|
+
.empty { color:#64748b; }
|
|
44
|
+
.letter { margin-top:9px;padding:10px;border:1px solid #ef444466;border-radius:9px;overflow-wrap:anywhere; }
|
|
45
|
+
.letter strong { display:block; }
|
|
46
|
+
.meta { color:#64748b;margin:4px 0 8px; }
|
|
47
|
+
.actions { display:flex;flex-wrap:wrap;gap:6px; }
|
|
48
|
+
.danger { color:#b91c1c; }
|
|
49
|
+
.notice { margin-bottom:9px;padding:8px;border-radius:7px;background:#f59e0b22; }
|
|
50
|
+
@media (prefers-color-scheme:dark) { .panel { background:#111827f2;color:#f8fafc; } .empty,.meta { color:#94a3b8; } .danger { color:#fca5a5; } }
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const renderInspection = (
|
|
54
|
+
body: HTMLElement,
|
|
55
|
+
inspection: SyncRuntimeInspection,
|
|
56
|
+
notice?: string
|
|
57
|
+
) => {
|
|
58
|
+
body.replaceChildren();
|
|
59
|
+
if (notice) {
|
|
60
|
+
const message = document.createElement('div');
|
|
61
|
+
message.className = 'notice';
|
|
62
|
+
message.textContent = notice;
|
|
63
|
+
body.appendChild(message);
|
|
64
|
+
}
|
|
65
|
+
const metrics = document.createElement('div');
|
|
66
|
+
metrics.className = 'metrics';
|
|
67
|
+
for (const [label, value] of [
|
|
68
|
+
['Pending', inspection.pending],
|
|
69
|
+
['Dead letters', inspection.deadLetters.length],
|
|
70
|
+
['Conflicts', inspection.conflicts],
|
|
71
|
+
['Auto-resolved', inspection.automaticResolutions]
|
|
72
|
+
] as const) {
|
|
73
|
+
const metric = document.createElement('div');
|
|
74
|
+
metric.className = 'metric';
|
|
75
|
+
const strong = document.createElement('strong');
|
|
76
|
+
strong.textContent = String(value);
|
|
77
|
+
metric.append(strong, label);
|
|
78
|
+
metrics.appendChild(metric);
|
|
79
|
+
}
|
|
80
|
+
body.appendChild(metrics);
|
|
81
|
+
const activity = document.createElement('div');
|
|
82
|
+
activity.className = 'meta';
|
|
83
|
+
activity.textContent = `Clients ${inspection.clients} · last push ${time(inspection.lastSuccessfulPushAt)} · last pull ${time(inspection.lastSuccessfulPullAt)}`;
|
|
84
|
+
body.appendChild(activity);
|
|
85
|
+
if (inspection.deadLetters.length === 0) {
|
|
86
|
+
const empty = document.createElement('div');
|
|
87
|
+
empty.className = 'empty';
|
|
88
|
+
empty.textContent = 'No mutations need manual remediation.';
|
|
89
|
+
body.appendChild(empty);
|
|
90
|
+
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const deadLetter of inspection.deadLetters) {
|
|
94
|
+
const item = document.createElement('section');
|
|
95
|
+
item.className = 'letter';
|
|
96
|
+
const title = document.createElement('strong');
|
|
97
|
+
title.textContent = deadLetter.name;
|
|
98
|
+
const metadata = document.createElement('div');
|
|
99
|
+
metadata.className = 'meta';
|
|
100
|
+
metadata.textContent = `${deadLetter.kind ?? 'rejected'}${deadLetter.code ? ` · ${deadLetter.code}` : ''} · attempts ${deadLetter.attempts} · ${time(deadLetter.deadLetteredAt)}`;
|
|
101
|
+
const detail = document.createElement('div');
|
|
102
|
+
detail.textContent =
|
|
103
|
+
deadLetter.message ?? 'The server rejected this mutation.';
|
|
104
|
+
const actions = document.createElement('div');
|
|
105
|
+
actions.className = 'actions';
|
|
106
|
+
for (const [action, label] of [
|
|
107
|
+
['retry', 'Retry unchanged'],
|
|
108
|
+
['rebase', 'Rebase with new args'],
|
|
109
|
+
['discard', 'Discard']
|
|
110
|
+
] as const) {
|
|
111
|
+
const button = document.createElement('button');
|
|
112
|
+
button.className = `action${action === 'discard' ? ' danger' : ''}`;
|
|
113
|
+
button.dataset.action = action;
|
|
114
|
+
button.dataset.operationId = deadLetter.operationId;
|
|
115
|
+
button.textContent = label;
|
|
116
|
+
actions.appendChild(button);
|
|
117
|
+
}
|
|
118
|
+
item.append(title, metadata, detail, actions);
|
|
119
|
+
body.appendChild(item);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** Install the development-only, framework-neutral native Sync panel. */
|
|
124
|
+
export const installAbsoluteNativeSyncDevtools = (
|
|
125
|
+
devtoolsBridge: SyncDevtoolsBridge = bridge
|
|
126
|
+
) => {
|
|
127
|
+
if (typeof document === 'undefined' || !document.body)
|
|
128
|
+
return () => undefined;
|
|
129
|
+
if (document.getElementById(DEVTOOLS_ID)) return () => undefined;
|
|
130
|
+
const host = document.createElement('aside');
|
|
131
|
+
host.id = DEVTOOLS_ID;
|
|
132
|
+
host.dataset.hmrOverlay = 'true';
|
|
133
|
+
const root = host.attachShadow({ mode: 'open' });
|
|
134
|
+
const style = document.createElement('style');
|
|
135
|
+
style.textContent = styles;
|
|
136
|
+
const trigger = document.createElement('button');
|
|
137
|
+
trigger.className = 'trigger';
|
|
138
|
+
trigger.textContent = 'Sync';
|
|
139
|
+
trigger.type = 'button';
|
|
140
|
+
const panel = document.createElement('section');
|
|
141
|
+
panel.className = 'panel';
|
|
142
|
+
panel.hidden = true;
|
|
143
|
+
const header = document.createElement('header');
|
|
144
|
+
const title = document.createElement('h2');
|
|
145
|
+
title.textContent = 'AbsoluteJS Sync';
|
|
146
|
+
const close = document.createElement('button');
|
|
147
|
+
close.className = 'close';
|
|
148
|
+
close.textContent = 'Close';
|
|
149
|
+
close.type = 'button';
|
|
150
|
+
header.append(title, close);
|
|
151
|
+
const body = document.createElement('div');
|
|
152
|
+
body.className = 'body';
|
|
153
|
+
panel.append(header, body);
|
|
154
|
+
root.append(style, trigger, panel);
|
|
155
|
+
document.body.appendChild(host);
|
|
156
|
+
let active = true;
|
|
157
|
+
let notice: string | undefined;
|
|
158
|
+
const refresh = async () => {
|
|
159
|
+
try {
|
|
160
|
+
const inspection = await devtoolsBridge.inspect();
|
|
161
|
+
if (!active) return;
|
|
162
|
+
trigger.dataset.alert = String(inspection.deadLetters.length > 0);
|
|
163
|
+
trigger.textContent = inspection.deadLetters.length
|
|
164
|
+
? `Sync · ${inspection.deadLetters.length}`
|
|
165
|
+
: 'Sync';
|
|
166
|
+
if (!panel.hidden) renderInspection(body, inspection, notice);
|
|
167
|
+
notice = undefined;
|
|
168
|
+
} catch {
|
|
169
|
+
if (!active || panel.hidden) return;
|
|
170
|
+
notice = 'Sync diagnostics are temporarily unavailable.';
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
trigger.addEventListener('click', () => {
|
|
174
|
+
panel.hidden = !panel.hidden;
|
|
175
|
+
if (!panel.hidden) void refresh();
|
|
176
|
+
});
|
|
177
|
+
close.addEventListener('click', () => {
|
|
178
|
+
panel.hidden = true;
|
|
179
|
+
});
|
|
180
|
+
const remediate = async (
|
|
181
|
+
action: string | undefined,
|
|
182
|
+
operationId: string
|
|
183
|
+
) => {
|
|
184
|
+
try {
|
|
185
|
+
if (action === 'retry') await devtoolsBridge.retry(operationId);
|
|
186
|
+
else if (action === 'discard') {
|
|
187
|
+
if (
|
|
188
|
+
!globalThis.confirm(
|
|
189
|
+
'Discard this local mutation permanently?'
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
return;
|
|
193
|
+
await devtoolsBridge.discard(operationId);
|
|
194
|
+
} else if (action === 'rebase') {
|
|
195
|
+
const serialized = globalThis.prompt(
|
|
196
|
+
'New mutation arguments as JSON. This creates a new operation intent:'
|
|
197
|
+
);
|
|
198
|
+
if (serialized === null) return;
|
|
199
|
+
let args: unknown;
|
|
200
|
+
try {
|
|
201
|
+
args = JSON.parse(serialized);
|
|
202
|
+
} catch {
|
|
203
|
+
notice = 'Rebase cancelled: arguments were not valid JSON.';
|
|
204
|
+
await refresh();
|
|
205
|
+
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (
|
|
209
|
+
!globalThis.confirm(
|
|
210
|
+
'Create a new mutation with these arguments?'
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
return;
|
|
214
|
+
await devtoolsBridge.rebase(operationId, args);
|
|
215
|
+
}
|
|
216
|
+
notice = 'Sync remediation applied.';
|
|
217
|
+
} catch {
|
|
218
|
+
notice =
|
|
219
|
+
'Sync remediation failed. The local mutation was retained.';
|
|
220
|
+
}
|
|
221
|
+
await refresh();
|
|
222
|
+
};
|
|
223
|
+
body.addEventListener('click', (event) => {
|
|
224
|
+
if (!(event.target instanceof HTMLButtonElement)) return;
|
|
225
|
+
const { action, operationId } = event.target.dataset;
|
|
226
|
+
if (!operationId) return;
|
|
227
|
+
void remediate(action, operationId);
|
|
228
|
+
});
|
|
229
|
+
const interval = setInterval(() => void refresh(), REFRESH_INTERVAL_MS);
|
|
230
|
+
void refresh();
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
active = false;
|
|
234
|
+
clearInterval(interval);
|
|
235
|
+
host.remove();
|
|
236
|
+
};
|
|
237
|
+
};
|