@korajs/devtools 0.1.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/dist/chunk-4ZQ2RTZM.js +203 -0
- package/dist/chunk-4ZQ2RTZM.js.map +1 -0
- package/dist/chunk-JH2X4T4Z.js +58 -0
- package/dist/chunk-JH2X4T4Z.js.map +1 -0
- package/dist/extension/background.cjs +65 -0
- package/dist/extension/background.cjs.map +1 -0
- package/dist/extension/background.d.cts +2 -0
- package/dist/extension/background.d.ts +2 -0
- package/dist/extension/background.js +13 -0
- package/dist/extension/background.js.map +1 -0
- package/dist/extension/content-script.cjs +18 -0
- package/dist/extension/content-script.cjs.map +1 -0
- package/dist/extension/content-script.d.cts +2 -0
- package/dist/extension/content-script.d.ts +2 -0
- package/dist/extension/content-script.js +16 -0
- package/dist/extension/content-script.js.map +1 -0
- package/dist/extension/devtools-page.html +45 -0
- package/dist/extension/devtools.cjs +9 -0
- package/dist/extension/devtools.cjs.map +1 -0
- package/dist/extension/devtools.d.cts +2 -0
- package/dist/extension/devtools.d.ts +2 -0
- package/dist/extension/devtools.js +7 -0
- package/dist/extension/devtools.js.map +1 -0
- package/dist/extension/manifest.json +20 -0
- package/dist/extension/panel.cjs +220 -0
- package/dist/extension/panel.cjs.map +1 -0
- package/dist/extension/panel.d.cts +2 -0
- package/dist/extension/panel.d.ts +2 -0
- package/dist/extension/panel.js +24 -0
- package/dist/extension/panel.js.map +1 -0
- package/dist/index.cjs +662 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +260 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.js +383 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/ui/panel-state.ts
|
|
4
|
+
function buildPanelModel(events2) {
|
|
5
|
+
const timeline = events2.map((entry) => ({
|
|
6
|
+
id: entry.id,
|
|
7
|
+
type: entry.event.type,
|
|
8
|
+
label: timelineLabel(entry.event),
|
|
9
|
+
color: timelineColor(entry.event.type),
|
|
10
|
+
receivedAt: entry.receivedAt,
|
|
11
|
+
dependsOn: extractCausalDependencies(entry.event)
|
|
12
|
+
}));
|
|
13
|
+
const conflicts = events2.flatMap((entry) => {
|
|
14
|
+
if (entry.event.type !== "merge:completed" && entry.event.type !== "merge:conflict") {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const trace = entry.event.trace;
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
id: entry.id,
|
|
21
|
+
timestamp: entry.receivedAt,
|
|
22
|
+
collection: trace.operationA.collection,
|
|
23
|
+
field: trace.field,
|
|
24
|
+
strategy: trace.strategy,
|
|
25
|
+
tier: trace.tier,
|
|
26
|
+
inputA: trace.inputA,
|
|
27
|
+
inputB: trace.inputB,
|
|
28
|
+
output: trace.output,
|
|
29
|
+
constraintViolated: trace.constraintViolated
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
});
|
|
33
|
+
const operations = events2.map((entry) => {
|
|
34
|
+
const operation = extractOperation(entry.event);
|
|
35
|
+
if (!operation) return null;
|
|
36
|
+
return {
|
|
37
|
+
id: entry.id,
|
|
38
|
+
timestamp: entry.receivedAt,
|
|
39
|
+
operationId: operation.id,
|
|
40
|
+
collection: operation.collection,
|
|
41
|
+
recordId: operation.recordId,
|
|
42
|
+
opType: operation.type,
|
|
43
|
+
data: operation.data,
|
|
44
|
+
causalDeps: operation.causalDeps,
|
|
45
|
+
nodeId: operation.nodeId,
|
|
46
|
+
sequenceNumber: operation.sequenceNumber
|
|
47
|
+
};
|
|
48
|
+
}).filter((item) => item !== null);
|
|
49
|
+
const network = buildNetworkStatus(events2, operations);
|
|
50
|
+
return {
|
|
51
|
+
timeline,
|
|
52
|
+
conflicts,
|
|
53
|
+
operations,
|
|
54
|
+
network
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function buildNetworkStatus(events2, operations) {
|
|
58
|
+
let connected = false;
|
|
59
|
+
let quality = null;
|
|
60
|
+
let pendingAcks = 0;
|
|
61
|
+
let lastSyncAt = null;
|
|
62
|
+
let sentOps = 0;
|
|
63
|
+
let receivedOps = 0;
|
|
64
|
+
for (const entry of events2) {
|
|
65
|
+
switch (entry.event.type) {
|
|
66
|
+
case "sync:connected":
|
|
67
|
+
connected = true;
|
|
68
|
+
lastSyncAt = entry.receivedAt;
|
|
69
|
+
break;
|
|
70
|
+
case "sync:disconnected":
|
|
71
|
+
connected = false;
|
|
72
|
+
break;
|
|
73
|
+
case "connection:quality":
|
|
74
|
+
quality = entry.event.quality;
|
|
75
|
+
break;
|
|
76
|
+
case "sync:sent":
|
|
77
|
+
sentOps += entry.event.operations.length;
|
|
78
|
+
pendingAcks += entry.event.operations.length;
|
|
79
|
+
lastSyncAt = entry.receivedAt;
|
|
80
|
+
break;
|
|
81
|
+
case "sync:received":
|
|
82
|
+
receivedOps += entry.event.operations.length;
|
|
83
|
+
lastSyncAt = entry.receivedAt;
|
|
84
|
+
break;
|
|
85
|
+
case "sync:acknowledged":
|
|
86
|
+
pendingAcks = Math.max(0, pendingAcks - 1);
|
|
87
|
+
lastSyncAt = entry.receivedAt;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const vector = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const operation of operations) {
|
|
93
|
+
const current = vector.get(operation.nodeId) ?? 0;
|
|
94
|
+
if (operation.sequenceNumber > current) {
|
|
95
|
+
vector.set(operation.nodeId, operation.sequenceNumber);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
connected,
|
|
100
|
+
quality,
|
|
101
|
+
pendingAcks,
|
|
102
|
+
lastSyncAt,
|
|
103
|
+
sentOps,
|
|
104
|
+
receivedOps,
|
|
105
|
+
versionVector: [...vector.entries()].map(([nodeId, sequenceNumber]) => ({ nodeId, sequenceNumber })).sort((left, right) => left.nodeId.localeCompare(right.nodeId))
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function timelineLabel(event) {
|
|
109
|
+
switch (event.type) {
|
|
110
|
+
case "operation:created":
|
|
111
|
+
case "operation:applied":
|
|
112
|
+
return `${event.operation.type} ${event.operation.collection}/${event.operation.recordId}`;
|
|
113
|
+
case "merge:started":
|
|
114
|
+
return `merge start ${event.operationA.collection}`;
|
|
115
|
+
case "merge:completed":
|
|
116
|
+
return `merge complete ${event.trace.field}`;
|
|
117
|
+
case "merge:conflict":
|
|
118
|
+
return `merge conflict ${event.trace.field}`;
|
|
119
|
+
case "constraint:violated":
|
|
120
|
+
return `constraint ${event.constraint}`;
|
|
121
|
+
case "sync:connected":
|
|
122
|
+
return `sync connected ${event.nodeId}`;
|
|
123
|
+
case "sync:disconnected":
|
|
124
|
+
return `sync disconnected`;
|
|
125
|
+
case "sync:sent":
|
|
126
|
+
return `sync sent ${event.batchSize}`;
|
|
127
|
+
case "sync:received":
|
|
128
|
+
return `sync received ${event.batchSize}`;
|
|
129
|
+
case "sync:acknowledged":
|
|
130
|
+
return `sync ack ${event.sequenceNumber}`;
|
|
131
|
+
case "query:subscribed":
|
|
132
|
+
return `query subscribed ${event.collection}`;
|
|
133
|
+
case "query:invalidated":
|
|
134
|
+
return `query invalidated ${event.queryId}`;
|
|
135
|
+
case "query:executed":
|
|
136
|
+
return `query executed ${event.queryId}`;
|
|
137
|
+
case "connection:quality":
|
|
138
|
+
return `connection ${event.quality}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function timelineColor(type) {
|
|
142
|
+
if (type.startsWith("operation:")) return "#22c55e";
|
|
143
|
+
if (type.startsWith("sync:")) return "#a855f7";
|
|
144
|
+
if (type.startsWith("merge:") || type.startsWith("constraint:")) return "#f59e0b";
|
|
145
|
+
if (type.startsWith("query:")) return "#0ea5e9";
|
|
146
|
+
return "#64748b";
|
|
147
|
+
}
|
|
148
|
+
function extractCausalDependencies(event) {
|
|
149
|
+
const operation = extractOperation(event);
|
|
150
|
+
return operation?.causalDeps ?? [];
|
|
151
|
+
}
|
|
152
|
+
function extractOperation(event) {
|
|
153
|
+
switch (event.type) {
|
|
154
|
+
case "operation:created":
|
|
155
|
+
case "operation:applied":
|
|
156
|
+
return event.operation;
|
|
157
|
+
case "query:invalidated":
|
|
158
|
+
return event.trigger;
|
|
159
|
+
default:
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/ui/panel.ts
|
|
165
|
+
function renderDevtoolsPanel(target, events2) {
|
|
166
|
+
const model = buildPanelModel(events2);
|
|
167
|
+
target.innerHTML = [
|
|
168
|
+
'<section data-panel="timeline"><h2>Sync Timeline</h2>',
|
|
169
|
+
`<p>Total events: ${model.timeline.length}</p>`,
|
|
170
|
+
"<ul>",
|
|
171
|
+
...model.timeline.slice(-20).map(
|
|
172
|
+
(item) => `<li><span style="color:${item.color}">${item.type}</span> \xB7 ${escapeHtml(item.label)}</li>`
|
|
173
|
+
),
|
|
174
|
+
"</ul></section>",
|
|
175
|
+
'<section data-panel="conflicts"><h2>Conflict Inspector</h2>',
|
|
176
|
+
`<p>Conflicts: ${model.conflicts.length}</p>`,
|
|
177
|
+
"<ul>",
|
|
178
|
+
...model.conflicts.slice(-20).map(
|
|
179
|
+
(item) => `<li>${escapeHtml(item.collection)}.${escapeHtml(item.field)} \xB7 ${escapeHtml(item.strategy)} \xB7 tier ${item.tier}</li>`
|
|
180
|
+
),
|
|
181
|
+
"</ul></section>",
|
|
182
|
+
'<section data-panel="operations"><h2>Operation Log</h2>',
|
|
183
|
+
`<p>Operations: ${model.operations.length}</p>`,
|
|
184
|
+
"<ul>",
|
|
185
|
+
...model.operations.slice(-20).map(
|
|
186
|
+
(item) => `<li>${escapeHtml(item.opType)} ${escapeHtml(item.collection)}/${escapeHtml(item.recordId)} (${escapeHtml(item.operationId)})</li>`
|
|
187
|
+
),
|
|
188
|
+
"</ul></section>",
|
|
189
|
+
'<section data-panel="network"><h2>Network Status</h2>',
|
|
190
|
+
`<p>Connected: ${model.network.connected ? "yes" : "no"}</p>`,
|
|
191
|
+
`<p>Pending ACKs: ${model.network.pendingAcks}</p>`,
|
|
192
|
+
`<p>Sent ops: ${model.network.sentOps}</p>`,
|
|
193
|
+
`<p>Received ops: ${model.network.receivedOps}</p>`,
|
|
194
|
+
"</section>"
|
|
195
|
+
].join("");
|
|
196
|
+
}
|
|
197
|
+
function escapeHtml(value) {
|
|
198
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/extension/panel.ts
|
|
202
|
+
var extensionRoot = document.getElementById("kora-devtools-root");
|
|
203
|
+
if (!extensionRoot) {
|
|
204
|
+
throw new Error("Missing #kora-devtools-root element");
|
|
205
|
+
}
|
|
206
|
+
var runtime = globalThis.chrome?.runtime;
|
|
207
|
+
var devtools = globalThis.chrome?.devtools;
|
|
208
|
+
var events = [];
|
|
209
|
+
if (runtime && devtools) {
|
|
210
|
+
const port = runtime.connect({ name: "kora-panel" });
|
|
211
|
+
port.postMessage({ type: "panel-init", tabId: devtools.inspectedWindow.tabId });
|
|
212
|
+
port.onMessage.addListener((message) => {
|
|
213
|
+
const typed = message;
|
|
214
|
+
if (!typed || typed.type !== "kora-event" || !typed.payload) return;
|
|
215
|
+
events.push(typed.payload);
|
|
216
|
+
renderDevtoolsPanel(extensionRoot, events);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
renderDevtoolsPanel(extensionRoot, events);
|
|
220
|
+
//# sourceMappingURL=panel.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/ui/panel-state.ts","../../src/ui/panel.ts","../../src/extension/panel.ts"],"sourcesContent":["import type { KoraEvent, Operation } from '@korajs/core'\nimport type { TimestampedEvent } from '../types'\n\nexport interface TimelineItem {\n\tid: number\n\ttype: KoraEvent['type']\n\tlabel: string\n\tcolor: string\n\treceivedAt: number\n\tdependsOn: string[]\n}\n\nexport interface ConflictItem {\n\tid: number\n\ttimestamp: number\n\tcollection: string\n\tfield: string\n\tstrategy: string\n\ttier: 1 | 2 | 3\n\tinputA: unknown\n\tinputB: unknown\n\toutput: unknown\n\tconstraintViolated: string | null\n}\n\nexport interface OperationItem {\n\tid: number\n\ttimestamp: number\n\toperationId: string\n\tcollection: string\n\trecordId: string\n\topType: Operation['type']\n\tdata: Record<string, unknown> | null\n\tcausalDeps: string[]\n\tnodeId: string\n\tsequenceNumber: number\n}\n\nexport interface NetworkStatusModel {\n\tconnected: boolean\n\tquality: string | null\n\tpendingAcks: number\n\tlastSyncAt: number | null\n\tsentOps: number\n\treceivedOps: number\n\tversionVector: Array<{ nodeId: string; sequenceNumber: number }>\n}\n\nexport interface DevtoolsPanelModel {\n\ttimeline: TimelineItem[]\n\tconflicts: ConflictItem[]\n\toperations: OperationItem[]\n\tnetwork: NetworkStatusModel\n}\n\nexport function buildPanelModel(events: readonly TimestampedEvent[]): DevtoolsPanelModel {\n\tconst timeline = events.map((entry) => ({\n\t\tid: entry.id,\n\t\ttype: entry.event.type,\n\t\tlabel: timelineLabel(entry.event),\n\t\tcolor: timelineColor(entry.event.type),\n\t\treceivedAt: entry.receivedAt,\n\t\tdependsOn: extractCausalDependencies(entry.event),\n\t}))\n\n\tconst conflicts = events\n\t\t.flatMap((entry) => {\n\t\t\tif (entry.event.type !== 'merge:completed' && entry.event.type !== 'merge:conflict') {\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tconst trace = entry.event.trace\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tid: entry.id,\n\t\t\t\t\ttimestamp: entry.receivedAt,\n\t\t\t\t\tcollection: trace.operationA.collection,\n\t\t\t\t\tfield: trace.field,\n\t\t\t\t\tstrategy: trace.strategy,\n\t\t\t\t\ttier: trace.tier,\n\t\t\t\t\tinputA: trace.inputA,\n\t\t\t\t\tinputB: trace.inputB,\n\t\t\t\t\toutput: trace.output,\n\t\t\t\t\tconstraintViolated: trace.constraintViolated,\n\t\t\t\t},\n\t\t\t]\n\t\t})\n\n\tconst operations = events\n\t\t.map((entry) => {\n\t\t\tconst operation = extractOperation(entry.event)\n\t\t\tif (!operation) return null\n\n\t\t\treturn {\n\t\t\t\tid: entry.id,\n\t\t\t\ttimestamp: entry.receivedAt,\n\t\t\t\toperationId: operation.id,\n\t\t\t\tcollection: operation.collection,\n\t\t\t\trecordId: operation.recordId,\n\t\t\t\topType: operation.type,\n\t\t\t\tdata: operation.data,\n\t\t\t\tcausalDeps: operation.causalDeps,\n\t\t\t\tnodeId: operation.nodeId,\n\t\t\t\tsequenceNumber: operation.sequenceNumber,\n\t\t\t}\n\t\t})\n\t\t.filter((item): item is OperationItem => item !== null)\n\n\tconst network = buildNetworkStatus(events, operations)\n\n\treturn {\n\t\ttimeline,\n\t\tconflicts,\n\t\toperations,\n\t\tnetwork,\n\t}\n}\n\nfunction buildNetworkStatus(\n\tevents: readonly TimestampedEvent[],\n\toperations: readonly OperationItem[],\n): NetworkStatusModel {\n\tlet connected = false\n\tlet quality: string | null = null\n\tlet pendingAcks = 0\n\tlet lastSyncAt: number | null = null\n\tlet sentOps = 0\n\tlet receivedOps = 0\n\n\tfor (const entry of events) {\n\t\tswitch (entry.event.type) {\n\t\t\tcase 'sync:connected':\n\t\t\t\tconnected = true\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:disconnected':\n\t\t\t\tconnected = false\n\t\t\t\tbreak\n\t\t\tcase 'connection:quality':\n\t\t\t\tquality = entry.event.quality\n\t\t\t\tbreak\n\t\t\tcase 'sync:sent':\n\t\t\t\tsentOps += entry.event.operations.length\n\t\t\t\tpendingAcks += entry.event.operations.length\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:received':\n\t\t\t\treceivedOps += entry.event.operations.length\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:acknowledged':\n\t\t\t\tpendingAcks = Math.max(0, pendingAcks - 1)\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\tconst vector = new Map<string, number>()\n\tfor (const operation of operations) {\n\t\tconst current = vector.get(operation.nodeId) ?? 0\n\t\tif (operation.sequenceNumber > current) {\n\t\t\tvector.set(operation.nodeId, operation.sequenceNumber)\n\t\t}\n\t}\n\n\treturn {\n\t\tconnected,\n\t\tquality,\n\t\tpendingAcks,\n\t\tlastSyncAt,\n\t\tsentOps,\n\t\treceivedOps,\n\t\tversionVector: [...vector.entries()]\n\t\t\t.map(([nodeId, sequenceNumber]) => ({ nodeId, sequenceNumber }))\n\t\t\t.sort((left, right) => left.nodeId.localeCompare(right.nodeId)),\n\t}\n}\n\nfunction timelineLabel(event: KoraEvent): string {\n\tswitch (event.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn `${event.operation.type} ${event.operation.collection}/${event.operation.recordId}`\n\t\tcase 'merge:started':\n\t\t\treturn `merge start ${event.operationA.collection}`\n\t\tcase 'merge:completed':\n\t\t\treturn `merge complete ${event.trace.field}`\n\t\tcase 'merge:conflict':\n\t\t\treturn `merge conflict ${event.trace.field}`\n\t\tcase 'constraint:violated':\n\t\t\treturn `constraint ${event.constraint}`\n\t\tcase 'sync:connected':\n\t\t\treturn `sync connected ${event.nodeId}`\n\t\tcase 'sync:disconnected':\n\t\t\treturn `sync disconnected`\n\t\tcase 'sync:sent':\n\t\t\treturn `sync sent ${event.batchSize}`\n\t\tcase 'sync:received':\n\t\t\treturn `sync received ${event.batchSize}`\n\t\tcase 'sync:acknowledged':\n\t\t\treturn `sync ack ${event.sequenceNumber}`\n\t\tcase 'query:subscribed':\n\t\t\treturn `query subscribed ${event.collection}`\n\t\tcase 'query:invalidated':\n\t\t\treturn `query invalidated ${event.queryId}`\n\t\tcase 'query:executed':\n\t\t\treturn `query executed ${event.queryId}`\n\t\tcase 'connection:quality':\n\t\t\treturn `connection ${event.quality}`\n\t}\n}\n\nfunction timelineColor(type: KoraEvent['type']): string {\n\tif (type.startsWith('operation:')) return '#22c55e'\n\tif (type.startsWith('sync:')) return '#a855f7'\n\tif (type.startsWith('merge:') || type.startsWith('constraint:')) return '#f59e0b'\n\tif (type.startsWith('query:')) return '#0ea5e9'\n\treturn '#64748b'\n}\n\nfunction extractCausalDependencies(event: KoraEvent): string[] {\n\tconst operation = extractOperation(event)\n\treturn operation?.causalDeps ?? []\n}\n\nfunction extractOperation(event: KoraEvent): Operation | null {\n\tswitch (event.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn event.operation\n\t\tcase 'query:invalidated':\n\t\t\treturn event.trigger\n\t\tdefault:\n\t\t\treturn null\n\t}\n}\n","import type { TimestampedEvent } from '../types'\nimport { buildPanelModel } from './panel-state'\n\nexport function renderDevtoolsPanel(target: HTMLElement, events: readonly TimestampedEvent[]): void {\n\tconst model = buildPanelModel(events)\n\n\ttarget.innerHTML = [\n\t\t'<section data-panel=\"timeline\"><h2>Sync Timeline</h2>',\n\t\t`<p>Total events: ${model.timeline.length}</p>`,\n\t\t'<ul>',\n\t\t...model.timeline.slice(-20).map(\n\t\t\t(item) =>\n\t\t\t\t`<li><span style=\"color:${item.color}\">${item.type}</span> · ${escapeHtml(item.label)}</li>`,\n\t\t),\n\t\t'</ul></section>',\n\t\t'<section data-panel=\"conflicts\"><h2>Conflict Inspector</h2>',\n\t\t`<p>Conflicts: ${model.conflicts.length}</p>`,\n\t\t'<ul>',\n\t\t...model.conflicts\n\t\t\t.slice(-20)\n\t\t\t.map(\n\t\t\t\t(item) =>\n\t\t\t\t\t`<li>${escapeHtml(item.collection)}.${escapeHtml(item.field)} · ${escapeHtml(item.strategy)} · tier ${item.tier}</li>`,\n\t\t\t),\n\t\t'</ul></section>',\n\t\t'<section data-panel=\"operations\"><h2>Operation Log</h2>',\n\t\t`<p>Operations: ${model.operations.length}</p>`,\n\t\t'<ul>',\n\t\t...model.operations.slice(-20).map(\n\t\t\t(item) =>\n\t\t\t\t`<li>${escapeHtml(item.opType)} ${escapeHtml(item.collection)}/${escapeHtml(item.recordId)} (${escapeHtml(item.operationId)})</li>`,\n\t\t),\n\t\t'</ul></section>',\n\t\t'<section data-panel=\"network\"><h2>Network Status</h2>',\n\t\t`<p>Connected: ${model.network.connected ? 'yes' : 'no'}</p>`,\n\t\t`<p>Pending ACKs: ${model.network.pendingAcks}</p>`,\n\t\t`<p>Sent ops: ${model.network.sentOps}</p>`,\n\t\t`<p>Received ops: ${model.network.receivedOps}</p>`,\n\t\t'</section>',\n\t].join('')\n}\n\nfunction escapeHtml(value: string): string {\n\treturn value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')\n}\n","import type { TimestampedEvent } from '../types'\nimport { renderDevtoolsPanel } from '../ui/panel'\n\ninterface RuntimePort {\n\tonMessage: {\n\t\taddListener(callback: (message: unknown) => void): void\n\t}\n\tpostMessage(message: unknown): void\n}\n\ninterface RuntimeLike {\n\tconnect(info: { name: string }): RuntimePort\n}\n\ninterface DevtoolsLike {\n\tinspectedWindow: { tabId: number }\n}\n\nconst extensionRoot = document.getElementById('kora-devtools-root')\nif (!extensionRoot) {\n\tthrow new Error('Missing #kora-devtools-root element')\n}\n\nconst runtime = (globalThis as { chrome?: { runtime?: RuntimeLike } }).chrome?.runtime\nconst devtools = (globalThis as { chrome?: { devtools?: DevtoolsLike } }).chrome?.devtools\n\nconst events: TimestampedEvent[] = []\n\nif (runtime && devtools) {\n\tconst port = runtime.connect({ name: 'kora-panel' })\n\tport.postMessage({ type: 'panel-init', tabId: devtools.inspectedWindow.tabId })\n\n\tport.onMessage.addListener((message) => {\n\t\tconst typed = message as { type?: string; payload?: TimestampedEvent } | undefined\n\t\tif (!typed || typed.type !== 'kora-event' || !typed.payload) return\n\n\t\tevents.push(typed.payload)\n\t\trenderDevtoolsPanel(extensionRoot, events)\n\t})\n}\n\nrenderDevtoolsPanel(extensionRoot, events)\n"],"mappings":"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","names":["events","events"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderDevtoolsPanel
|
|
3
|
+
} from "../chunk-4ZQ2RTZM.js";
|
|
4
|
+
|
|
5
|
+
// src/extension/panel.ts
|
|
6
|
+
var extensionRoot = document.getElementById("kora-devtools-root");
|
|
7
|
+
if (!extensionRoot) {
|
|
8
|
+
throw new Error("Missing #kora-devtools-root element");
|
|
9
|
+
}
|
|
10
|
+
var runtime = globalThis.chrome?.runtime;
|
|
11
|
+
var devtools = globalThis.chrome?.devtools;
|
|
12
|
+
var events = [];
|
|
13
|
+
if (runtime && devtools) {
|
|
14
|
+
const port = runtime.connect({ name: "kora-panel" });
|
|
15
|
+
port.postMessage({ type: "panel-init", tabId: devtools.inspectedWindow.tabId });
|
|
16
|
+
port.onMessage.addListener((message) => {
|
|
17
|
+
const typed = message;
|
|
18
|
+
if (!typed || typed.type !== "kora-event" || !typed.payload) return;
|
|
19
|
+
events.push(typed.payload);
|
|
20
|
+
renderDevtoolsPanel(extensionRoot, events);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
renderDevtoolsPanel(extensionRoot, events);
|
|
24
|
+
//# sourceMappingURL=panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/extension/panel.ts"],"sourcesContent":["import type { TimestampedEvent } from '../types'\nimport { renderDevtoolsPanel } from '../ui/panel'\n\ninterface RuntimePort {\n\tonMessage: {\n\t\taddListener(callback: (message: unknown) => void): void\n\t}\n\tpostMessage(message: unknown): void\n}\n\ninterface RuntimeLike {\n\tconnect(info: { name: string }): RuntimePort\n}\n\ninterface DevtoolsLike {\n\tinspectedWindow: { tabId: number }\n}\n\nconst extensionRoot = document.getElementById('kora-devtools-root')\nif (!extensionRoot) {\n\tthrow new Error('Missing #kora-devtools-root element')\n}\n\nconst runtime = (globalThis as { chrome?: { runtime?: RuntimeLike } }).chrome?.runtime\nconst devtools = (globalThis as { chrome?: { devtools?: DevtoolsLike } }).chrome?.devtools\n\nconst events: TimestampedEvent[] = []\n\nif (runtime && devtools) {\n\tconst port = runtime.connect({ name: 'kora-panel' })\n\tport.postMessage({ type: 'panel-init', tabId: devtools.inspectedWindow.tabId })\n\n\tport.onMessage.addListener((message) => {\n\t\tconst typed = message as { type?: string; payload?: TimestampedEvent } | undefined\n\t\tif (!typed || typed.type !== 'kora-event' || !typed.payload) return\n\n\t\tevents.push(typed.payload)\n\t\trenderDevtoolsPanel(extensionRoot, events)\n\t})\n}\n\nrenderDevtoolsPanel(extensionRoot, events)\n"],"mappings":";;;;;AAkBA,IAAM,gBAAgB,SAAS,eAAe,oBAAoB;AAClE,IAAI,CAAC,eAAe;AACnB,QAAM,IAAI,MAAM,qCAAqC;AACtD;AAEA,IAAM,UAAW,WAAsD,QAAQ;AAC/E,IAAM,WAAY,WAAwD,QAAQ;AAElF,IAAM,SAA6B,CAAC;AAEpC,IAAI,WAAW,UAAU;AACxB,QAAM,OAAO,QAAQ,QAAQ,EAAE,MAAM,aAAa,CAAC;AACnD,OAAK,YAAY,EAAE,MAAM,cAAc,OAAO,SAAS,gBAAgB,MAAM,CAAC;AAE9E,OAAK,UAAU,YAAY,CAAC,YAAY;AACvC,UAAM,QAAQ;AACd,QAAI,CAAC,SAAS,MAAM,SAAS,gBAAgB,CAAC,MAAM,QAAS;AAE7D,WAAO,KAAK,MAAM,OAAO;AACzB,wBAAoB,eAAe,MAAM;AAAA,EAC1C,CAAC;AACF;AAEA,oBAAoB,eAAe,MAAM;","names":[]}
|