latchkey 1.0.0 → 1.0.1

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 (44) hide show
  1. package/README.md +10 -6
  2. package/dist/package.json +1 -1
  3. package/dist/scripts/codegen/codeGenerator.d.ts +27 -0
  4. package/dist/scripts/codegen/codeGenerator.d.ts.map +1 -0
  5. package/dist/scripts/codegen/codeGenerator.js +220 -0
  6. package/dist/scripts/codegen/codeGenerator.js.map +1 -0
  7. package/dist/scripts/codegen/index.d.ts +27 -0
  8. package/dist/scripts/codegen/index.d.ts.map +1 -0
  9. package/dist/scripts/codegen/index.js +189 -0
  10. package/dist/scripts/codegen/index.js.map +1 -0
  11. package/dist/scripts/codegen/injectedScript.d.ts +6 -0
  12. package/dist/scripts/codegen/injectedScript.d.ts.map +1 -0
  13. package/dist/scripts/codegen/injectedScript.js +657 -0
  14. package/dist/scripts/codegen/injectedScript.js.map +1 -0
  15. package/dist/scripts/codegen/requestMetadataCollector.d.ts +15 -0
  16. package/dist/scripts/codegen/requestMetadataCollector.d.ts.map +1 -0
  17. package/dist/scripts/codegen/requestMetadataCollector.js +48 -0
  18. package/dist/scripts/codegen/requestMetadataCollector.js.map +1 -0
  19. package/dist/scripts/codegen/types.d.ts +77 -0
  20. package/dist/scripts/codegen/types.d.ts.map +1 -0
  21. package/dist/scripts/codegen/types.js +10 -0
  22. package/dist/scripts/codegen/types.js.map +1 -0
  23. package/dist/scripts/codegen.d.ts +24 -0
  24. package/dist/scripts/codegen.d.ts.map +1 -0
  25. package/dist/scripts/codegen.js +95 -0
  26. package/dist/scripts/codegen.js.map +1 -0
  27. package/dist/scripts/cryptFile.js +7 -2
  28. package/dist/scripts/cryptFile.js.map +1 -1
  29. package/dist/src/config.d.ts.map +1 -1
  30. package/dist/src/config.js +6 -12
  31. package/dist/src/config.js.map +1 -1
  32. package/dist/src/encryptedStorage.d.ts +1 -2
  33. package/dist/src/encryptedStorage.d.ts.map +1 -1
  34. package/dist/src/encryptedStorage.js +18 -38
  35. package/dist/src/encryptedStorage.js.map +1 -1
  36. package/dist/src/index.d.ts +1 -1
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +1 -1
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/keychain.d.ts +0 -4
  41. package/dist/src/keychain.d.ts.map +1 -1
  42. package/dist/src/keychain.js +0 -13
  43. package/dist/src/keychain.js.map +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1,657 @@
1
+ /**
2
+ * Creates the combined script injected into pages.
3
+ * Contains both the interaction recorder and the toolbar UI.
4
+ */
5
+ export function createInjectedScript() {
6
+ return `
7
+ (function() {
8
+ // ===== SHARED HELPER FUNCTIONS =====
9
+
10
+ // Get the implicit ARIA role for an element
11
+ function getImplicitRole(element) {
12
+ const tag = element.tagName.toLowerCase();
13
+ const type = element.type ? element.type.toLowerCase() : '';
14
+
15
+ const roleMap = {
16
+ 'a': element.href ? 'link' : null,
17
+ 'article': 'article',
18
+ 'aside': 'complementary',
19
+ 'button': 'button',
20
+ 'dialog': 'dialog',
21
+ 'form': 'form',
22
+ 'h1': 'heading',
23
+ 'h2': 'heading',
24
+ 'h3': 'heading',
25
+ 'h4': 'heading',
26
+ 'h5': 'heading',
27
+ 'h6': 'heading',
28
+ 'header': 'banner',
29
+ 'footer': 'contentinfo',
30
+ 'img': 'img',
31
+ 'input': getInputRole(type),
32
+ 'li': 'listitem',
33
+ 'main': 'main',
34
+ 'nav': 'navigation',
35
+ 'ol': 'list',
36
+ 'option': 'option',
37
+ 'progress': 'progressbar',
38
+ 'section': 'region',
39
+ 'select': 'combobox',
40
+ 'table': 'table',
41
+ 'textarea': 'textbox',
42
+ 'ul': 'list',
43
+ };
44
+
45
+ return roleMap[tag] || null;
46
+ }
47
+
48
+ function getInputRole(type) {
49
+ const inputRoles = {
50
+ 'button': 'button',
51
+ 'checkbox': 'checkbox',
52
+ 'email': 'textbox',
53
+ 'number': 'spinbutton',
54
+ 'radio': 'radio',
55
+ 'range': 'slider',
56
+ 'search': 'searchbox',
57
+ 'submit': 'button',
58
+ 'tel': 'textbox',
59
+ 'text': 'textbox',
60
+ 'url': 'textbox',
61
+ };
62
+ return inputRoles[type] || 'textbox';
63
+ }
64
+
65
+ // Get accessible name for an element
66
+ function getAccessibleName(element) {
67
+ // Check aria-label first
68
+ if (element.getAttribute('aria-label')) {
69
+ return element.getAttribute('aria-label');
70
+ }
71
+
72
+ // Check aria-labelledby
73
+ const labelledBy = element.getAttribute('aria-labelledby');
74
+ if (labelledBy) {
75
+ const labelEl = document.getElementById(labelledBy);
76
+ if (labelEl) {
77
+ return labelEl.textContent.trim();
78
+ }
79
+ }
80
+
81
+ // For inputs, check associated label
82
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') {
83
+ if (element.id) {
84
+ const label = document.querySelector('label[for="' + CSS.escape(element.id) + '"]');
85
+ if (label) {
86
+ return label.textContent.trim();
87
+ }
88
+ }
89
+ // Check for wrapping label
90
+ const parentLabel = element.closest('label');
91
+ if (parentLabel) {
92
+ // Get text content excluding the input itself
93
+ const clone = parentLabel.cloneNode(true);
94
+ const inputs = clone.querySelectorAll('input, textarea, select');
95
+ inputs.forEach(function(input) { input.remove(); });
96
+ const text = clone.textContent.trim();
97
+ if (text) return text;
98
+ }
99
+ }
100
+
101
+ // For buttons and links, use text content
102
+ const tag = element.tagName.toLowerCase();
103
+ if (tag === 'button' || tag === 'a') {
104
+ const text = element.textContent.trim();
105
+ if (text && text.length < 100) {
106
+ return text;
107
+ }
108
+ }
109
+
110
+ // Check title attribute
111
+ if (element.title) {
112
+ return element.title;
113
+ }
114
+
115
+ // For images, check alt
116
+ if (tag === 'img' && element.alt) {
117
+ return element.alt;
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ // Get element info for ancestry
124
+ function getElementInfo(element) {
125
+ const tag = element.tagName.toLowerCase();
126
+ const info = { tag: tag };
127
+
128
+ if (element.id) {
129
+ info.id = element.id;
130
+ }
131
+
132
+ if (element.className && typeof element.className === 'string' && element.className.trim()) {
133
+ info.className = element.className.trim();
134
+ }
135
+
136
+ if (element.name) {
137
+ info.name = element.name;
138
+ }
139
+
140
+ // Get role (explicit or implicit)
141
+ const explicitRole = element.getAttribute('role');
142
+ const role = explicitRole || getImplicitRole(element);
143
+ if (role) {
144
+ info.role = role;
145
+ }
146
+
147
+ // Get accessible name
148
+ const accessibleName = getAccessibleName(element);
149
+ if (accessibleName) {
150
+ info.accessibleName = accessibleName;
151
+ }
152
+
153
+ // For inputs, capture type
154
+ if (tag === 'input' && element.type) {
155
+ info.inputType = element.type.toLowerCase();
156
+ }
157
+
158
+ // Capture placeholder
159
+ if (element.placeholder) {
160
+ info.placeholder = element.placeholder;
161
+ }
162
+
163
+ return info;
164
+ }
165
+
166
+ // Get full ancestry from element to body
167
+ function getAncestry(element) {
168
+ const ancestry = [];
169
+ let current = element;
170
+
171
+ while (current && current !== document.body && current !== document.documentElement) {
172
+ ancestry.push(getElementInfo(current));
173
+ current = current.parentElement;
174
+ }
175
+
176
+ return ancestry;
177
+ }
178
+
179
+ // Check if element is part of our toolbar
180
+ function isToolbarElement(element) {
181
+ return element.closest && element.closest('#latchkey-recorder-toolbar');
182
+ }
183
+
184
+ // Global flag to track API key selection mode (set by toolbar, checked by recorder)
185
+ window.__latchkeyIsSelectingApiKey = false;
186
+
187
+ // ===== INTERACTION RECORDER =====
188
+
189
+ // Don't inject recorder twice
190
+ if (!window.__latchkeyRecorderInstalled) {
191
+ window.__latchkeyRecorderInstalled = true;
192
+
193
+ // Track clicks
194
+ document.addEventListener('click', (event) => {
195
+ const target = event.target;
196
+ // Don't record clicks when selecting API key element
197
+ if (!target || isToolbarElement(target) || window.__latchkeyIsSelectingApiKey) return;
198
+
199
+ const ancestry = getAncestry(target);
200
+
201
+ // Check if it's a checkbox or radio
202
+ if (target.tagName === 'INPUT') {
203
+ const inputType = target.type.toLowerCase();
204
+ if (inputType === 'checkbox') {
205
+ if (target.checked) {
206
+ window.__latchkeyRecordAction && window.__latchkeyRecordAction({
207
+ type: 'check',
208
+ ancestry: ancestry
209
+ });
210
+ } else {
211
+ window.__latchkeyRecordAction && window.__latchkeyRecordAction({
212
+ type: 'uncheck',
213
+ ancestry: ancestry
214
+ });
215
+ }
216
+ return;
217
+ }
218
+ }
219
+
220
+ window.__latchkeyRecordAction && window.__latchkeyRecordAction({
221
+ type: 'click',
222
+ ancestry: ancestry
223
+ });
224
+ }, true);
225
+
226
+ // Track input/change for fill actions
227
+ let lastInputElement = null;
228
+ let lastInputValue = '';
229
+ let lastInputAncestry = [];
230
+ let inputTimeout = null;
231
+
232
+ document.addEventListener('input', (event) => {
233
+ const target = event.target;
234
+ if (!target || isToolbarElement(target)) return;
235
+
236
+ const tagName = target.tagName;
237
+ if (tagName === 'INPUT' || tagName === 'TEXTAREA' || target.isContentEditable) {
238
+ const inputType = target.type ? target.type.toLowerCase() : 'text';
239
+
240
+ // Skip checkboxes and radios (handled by click)
241
+ if (inputType === 'checkbox' || inputType === 'radio') return;
242
+
243
+ lastInputElement = target;
244
+ lastInputValue = target.value || target.innerText || '';
245
+ lastInputAncestry = getAncestry(target);
246
+
247
+ // Debounce the recording to capture the final value
248
+ if (inputTimeout) clearTimeout(inputTimeout);
249
+ inputTimeout = setTimeout(() => {
250
+ if (lastInputElement) {
251
+ window.__latchkeyRecordAction && window.__latchkeyRecordAction({
252
+ type: 'fill',
253
+ ancestry: lastInputAncestry,
254
+ value: lastInputValue
255
+ });
256
+ lastInputElement = null;
257
+ lastInputValue = '';
258
+ lastInputAncestry = [];
259
+ }
260
+ }, 500);
261
+ }
262
+ }, true);
263
+
264
+ // Track select changes
265
+ document.addEventListener('change', (event) => {
266
+ const target = event.target;
267
+ if (!target || isToolbarElement(target)) return;
268
+
269
+ if (target.tagName === 'SELECT') {
270
+ const ancestry = getAncestry(target);
271
+ const selectedValue = target.value;
272
+ window.__latchkeyRecordAction && window.__latchkeyRecordAction({
273
+ type: 'select',
274
+ ancestry: ancestry,
275
+ value: selectedValue
276
+ });
277
+ }
278
+ }, true);
279
+
280
+ // Track key presses (for special keys like Enter, Tab, etc.)
281
+ document.addEventListener('keydown', (event) => {
282
+ const target = event.target;
283
+ if (!target || isToolbarElement(target)) return;
284
+
285
+ // Only record special keys
286
+ const specialKeys = ['Enter', 'Tab', 'Escape', 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
287
+ if (specialKeys.includes(event.key)) {
288
+ const ancestry = getAncestry(target);
289
+ window.__latchkeyRecordAction && window.__latchkeyRecordAction({
290
+ type: 'press',
291
+ ancestry: ancestry,
292
+ key: event.key
293
+ });
294
+ }
295
+ }, true);
296
+ }
297
+
298
+ // ===== TOOLBAR UI =====
299
+
300
+ function createAndInjectToolbar() {
301
+ // Don't inject toolbar twice
302
+ if (document.getElementById('latchkey-recorder-toolbar')) return;
303
+
304
+ // State
305
+ let isSelectingApiKeyElement = false;
306
+ let highlightOverlay = null;
307
+
308
+ // Create styles
309
+ const style = document.createElement('style');
310
+ style.textContent = \`
311
+ #latchkey-recorder-toolbar {
312
+ position: fixed;
313
+ top: 0;
314
+ left: 50%;
315
+ transform: translateX(-50%);
316
+ z-index: 2147483647;
317
+ background: #1a1a1a;
318
+ border-radius: 0 0 8px 8px;
319
+ padding: 8px 16px;
320
+ display: flex;
321
+ align-items: center;
322
+ gap: 8px;
323
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
324
+ font-size: 13px;
325
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
326
+ user-select: none;
327
+ }
328
+
329
+ #latchkey-recorder-toolbar * {
330
+ box-sizing: border-box;
331
+ }
332
+
333
+ .latchkey-toolbar-button {
334
+ background: #333;
335
+ border: 1px solid #555;
336
+ border-radius: 4px;
337
+ color: #fff;
338
+ padding: 6px 12px;
339
+ cursor: pointer;
340
+ font-size: 12px;
341
+ font-weight: 500;
342
+ transition: background 0.15s, border-color 0.15s;
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 6px;
346
+ white-space: nowrap;
347
+ }
348
+
349
+ .latchkey-toolbar-button:hover {
350
+ background: #444;
351
+ border-color: #666;
352
+ }
353
+
354
+ .latchkey-toolbar-button:active {
355
+ background: #555;
356
+ }
357
+
358
+ .latchkey-toolbar-button.active {
359
+ background: #2563eb;
360
+ border-color: #3b82f6;
361
+ }
362
+
363
+ .latchkey-toolbar-button.active:hover {
364
+ background: #1d4ed8;
365
+ border-color: #2563eb;
366
+ }
367
+
368
+ .latchkey-toolbar-button:disabled {
369
+ opacity: 0.5;
370
+ cursor: not-allowed;
371
+ }
372
+
373
+ .latchkey-toolbar-separator {
374
+ width: 1px;
375
+ height: 24px;
376
+ background: #444;
377
+ margin: 0 4px;
378
+ }
379
+
380
+ .latchkey-toolbar-status {
381
+ color: #aaa;
382
+ font-size: 11px;
383
+ margin-left: 8px;
384
+ min-width: 140px;
385
+ }
386
+
387
+ .latchkey-toolbar-status.pre-login {
388
+ color: #ff6b6b;
389
+ }
390
+
391
+ .latchkey-toolbar-status.post-login {
392
+ color: #4ade80;
393
+ }
394
+
395
+ .latchkey-phase-dot {
396
+ width: 8px;
397
+ height: 8px;
398
+ border-radius: 50%;
399
+ flex-shrink: 0;
400
+ }
401
+
402
+ .latchkey-phase-dot.pre-login {
403
+ background: #ff4444;
404
+ animation: latchkey-pulse 1.5s infinite;
405
+ }
406
+
407
+ .latchkey-phase-dot.post-login {
408
+ background: #4ade80;
409
+ animation: none;
410
+ }
411
+
412
+ @keyframes latchkey-pulse {
413
+ 0%, 100% { opacity: 1; }
414
+ 50% { opacity: 0.5; }
415
+ }
416
+
417
+ .latchkey-toolbar-gripper {
418
+ color: #666;
419
+ cursor: move;
420
+ padding: 4px;
421
+ display: flex;
422
+ align-items: center;
423
+ }
424
+
425
+ .latchkey-toolbar-gripper svg {
426
+ width: 16px;
427
+ height: 16px;
428
+ }
429
+
430
+ #latchkey-highlight-overlay {
431
+ position: fixed;
432
+ pointer-events: none;
433
+ border: 2px solid #22c55e;
434
+ background: rgba(34, 197, 94, 0.1);
435
+ z-index: 2147483646;
436
+ transition: all 0.05s ease-out;
437
+ }
438
+ \`;
439
+
440
+ // Add styles to head (or documentElement if head doesn't exist)
441
+ (document.head || document.documentElement).appendChild(style);
442
+
443
+ // Create highlight overlay for element picker
444
+ highlightOverlay = document.createElement('div');
445
+ highlightOverlay.id = 'latchkey-highlight-overlay';
446
+ highlightOverlay.style.display = 'none';
447
+ document.body.appendChild(highlightOverlay);
448
+
449
+ // Create toolbar
450
+ const toolbar = document.createElement('div');
451
+ toolbar.id = 'latchkey-recorder-toolbar';
452
+
453
+ // Gripper for dragging
454
+ const gripper = document.createElement('div');
455
+ gripper.className = 'latchkey-toolbar-gripper';
456
+ gripper.innerHTML = \`
457
+ <svg viewBox="0 0 16 16" fill="currentColor">
458
+ <path d="M5 3h2v2H5zm0 4h2v2H5zm0 4h2v2H5zm4-8h2v2H9zm0 4h2v2H9zm0 4h2v2H9z"/>
459
+ </svg>
460
+ \`;
461
+ toolbar.appendChild(gripper);
462
+
463
+ // Phase indicator dot
464
+ const phaseDot = document.createElement('div');
465
+ phaseDot.className = 'latchkey-phase-dot pre-login';
466
+ toolbar.appendChild(phaseDot);
467
+
468
+ // Status text
469
+ const status = document.createElement('span');
470
+ status.className = 'latchkey-toolbar-status pre-login';
471
+ status.textContent = 'Recording (pre-login)';
472
+ toolbar.appendChild(status);
473
+
474
+ // Separator
475
+ const sep1 = document.createElement('div');
476
+ sep1.className = 'latchkey-toolbar-separator';
477
+ toolbar.appendChild(sep1);
478
+
479
+ // "I've logged in" button - transitions from pre-login to post-login
480
+ const loggedInBtn = document.createElement('button');
481
+ loggedInBtn.className = 'latchkey-toolbar-button';
482
+ loggedInBtn.innerHTML = \`
483
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
484
+ <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/>
485
+ </svg>
486
+ I've logged in
487
+ \`;
488
+ loggedInBtn.title = 'Click after you have successfully logged in to start recording';
489
+ toolbar.appendChild(loggedInBtn);
490
+
491
+ // Separator
492
+ const sep2 = document.createElement('div');
493
+ sep2.className = 'latchkey-toolbar-separator';
494
+ toolbar.appendChild(sep2);
495
+
496
+ // "Select API Key Element" button
497
+ const apiKeyBtn = document.createElement('button');
498
+ apiKeyBtn.className = 'latchkey-toolbar-button';
499
+ apiKeyBtn.innerHTML = \`
500
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
501
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z"/>
502
+ </svg>
503
+ Select API Key Element
504
+ \`;
505
+ apiKeyBtn.title = 'Click to select the element containing the API key';
506
+ toolbar.appendChild(apiKeyBtn);
507
+
508
+ // Append toolbar to body
509
+ document.body.appendChild(toolbar);
510
+
511
+ // Function to update UI based on current phase
512
+ function updatePhaseUI(phase) {
513
+ // Update dot
514
+ phaseDot.className = 'latchkey-phase-dot ' + phase;
515
+
516
+ // Update status text
517
+ status.className = 'latchkey-toolbar-status ' + phase;
518
+ switch (phase) {
519
+ case 'pre-login':
520
+ status.textContent = 'Not recording (log in first)';
521
+ loggedInBtn.disabled = false;
522
+ break;
523
+ case 'post-login':
524
+ status.textContent = 'Recording';
525
+ loggedInBtn.disabled = true;
526
+ break;
527
+ }
528
+ }
529
+
530
+ // Button handler
531
+ loggedInBtn.onclick = () => {
532
+ if (loggedInBtn.disabled) return;
533
+ window.__latchkeyTransitionToPostLogin && window.__latchkeyTransitionToPostLogin();
534
+ updatePhaseUI('post-login');
535
+ };
536
+
537
+ apiKeyBtn.onclick = () => {
538
+ isSelectingApiKeyElement = !isSelectingApiKeyElement;
539
+ window.__latchkeyIsSelectingApiKey = isSelectingApiKeyElement;
540
+ apiKeyBtn.classList.toggle('active', isSelectingApiKeyElement);
541
+ highlightOverlay.style.display = isSelectingApiKeyElement ? 'block' : 'none';
542
+ if (isSelectingApiKeyElement) {
543
+ status.textContent = 'Click the API key element...';
544
+ } else {
545
+ // Restore status based on current phase (async)
546
+ if (window.__latchkeyGetPhase) {
547
+ window.__latchkeyGetPhase().then(function(phase) {
548
+ updatePhaseUI(phase || 'pre-login');
549
+ });
550
+ }
551
+ }
552
+ };
553
+
554
+ // Element picker: highlight on mousemove
555
+ document.addEventListener('mousemove', (e) => {
556
+ if (!isSelectingApiKeyElement) return;
557
+
558
+ const target = e.target;
559
+ if (!target || target === highlightOverlay || toolbar.contains(target)) {
560
+ highlightOverlay.style.display = 'none';
561
+ return;
562
+ }
563
+
564
+ const rect = target.getBoundingClientRect();
565
+ highlightOverlay.style.display = 'block';
566
+ highlightOverlay.style.left = rect.left + 'px';
567
+ highlightOverlay.style.top = rect.top + 'px';
568
+ highlightOverlay.style.width = rect.width + 'px';
569
+ highlightOverlay.style.height = rect.height + 'px';
570
+ }, true);
571
+
572
+ // Element picker: select on click
573
+ document.addEventListener('click', (e) => {
574
+ // Handle API key element selection
575
+ if (isSelectingApiKeyElement) {
576
+ const target = e.target;
577
+ if (!target || target === highlightOverlay || toolbar.contains(target)) {
578
+ return;
579
+ }
580
+
581
+ e.preventDefault();
582
+ e.stopPropagation();
583
+
584
+ isSelectingApiKeyElement = false;
585
+ window.__latchkeyIsSelectingApiKey = false;
586
+ apiKeyBtn.classList.remove('active');
587
+ highlightOverlay.style.display = 'none';
588
+
589
+ // Capture ancestry for the element
590
+ const ancestry = getAncestry(target);
591
+ window.__latchkeyApiKeyElementSelected && window.__latchkeyApiKeyElementSelected(ancestry);
592
+
593
+ // Restore status based on current phase (async)
594
+ if (window.__latchkeyGetPhase) {
595
+ window.__latchkeyGetPhase().then(function(phase) {
596
+ updatePhaseUI(phase || 'pre-login');
597
+ });
598
+ }
599
+ }
600
+ }, true);
601
+
602
+ // Make toolbar draggable
603
+ let isDragging = false;
604
+ let offsetX = 0;
605
+ let offsetY = 0;
606
+
607
+ gripper.addEventListener('mousedown', (e) => {
608
+ isDragging = true;
609
+ const rect = toolbar.getBoundingClientRect();
610
+ offsetX = e.clientX - rect.left;
611
+ offsetY = e.clientY - rect.top;
612
+ toolbar.style.transition = 'none';
613
+ e.preventDefault();
614
+ });
615
+
616
+ document.addEventListener('mousemove', (e) => {
617
+ if (!isDragging) return;
618
+ const x = e.clientX - offsetX;
619
+ const y = e.clientY - offsetY;
620
+ toolbar.style.left = x + 'px';
621
+ toolbar.style.top = y + 'px';
622
+ toolbar.style.transform = 'none';
623
+ });
624
+
625
+ document.addEventListener('mouseup', () => {
626
+ isDragging = false;
627
+ });
628
+
629
+ // Expose function to update UI when phase changes externally
630
+ window.__latchkeyUpdatePhase = updatePhaseUI;
631
+
632
+ // Initialize UI based on current phase (async because exposeFunction returns Promise)
633
+ if (window.__latchkeyGetPhase) {
634
+ window.__latchkeyGetPhase().then(function(phase) {
635
+ updatePhaseUI(phase || 'pre-login');
636
+ }).catch(function() {
637
+ updatePhaseUI('pre-login');
638
+ });
639
+ } else {
640
+ updatePhaseUI('pre-login');
641
+ }
642
+ }
643
+
644
+ // Wait for DOM to be ready before creating toolbar
645
+ if (document.body) {
646
+ createAndInjectToolbar();
647
+ } else if (document.readyState === 'loading') {
648
+ document.addEventListener('DOMContentLoaded', createAndInjectToolbar);
649
+ } else {
650
+ // readyState is 'interactive' or 'complete' but body doesn't exist yet
651
+ // Use a short timeout to wait for body
652
+ setTimeout(createAndInjectToolbar, 0);
653
+ }
654
+ })();
655
+ `;
656
+ }
657
+ //# sourceMappingURL=injectedScript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectedScript.js","sourceRoot":"","sources":["../../../scripts/codegen/injectedScript.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyoBR,CAAC;AACF,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Collects and manages HTTP request metadata.
3
+ */
4
+ import type { Request, Response } from 'playwright';
5
+ import type { RecordingPhase } from './types.js';
6
+ export declare class RequestMetadataCollector {
7
+ private readonly requests;
8
+ private readonly outputPath;
9
+ private currentPhase;
10
+ constructor(outputPath: string);
11
+ setPhase(phase: RecordingPhase): void;
12
+ addRequest(request: Request, response: Response | null): void;
13
+ flush(): void;
14
+ }
15
+ //# sourceMappingURL=requestMetadataCollector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requestMetadataCollector.d.ts","sourceRoot":"","sources":["../../../scripts/codegen/requestMetadataCollector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,YAAY,CAAC;AAElE,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,YAAY,CAA+B;gBAEvC,UAAU,EAAE,MAAM;IAI9B,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAIrC,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI;IAmC7D,KAAK,IAAI,IAAI;CAGd"}