@weave-apps/sdk 0.1.17 → 0.1.19

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.
@@ -1,620 +0,0 @@
1
- /**
2
- * Sidekick DOM API
3
- *
4
- * Client-side API for iframe apps to interact with the parent page DOM
5
- * through the secure DOM Bridge in the content script.
6
- */
7
- /**
8
- * DOM operation types (must match content script)
9
- */
10
- var DOMOperation;
11
- (function (DOMOperation) {
12
- // Read operations
13
- DOMOperation["QUERY"] = "QUERY";
14
- DOMOperation["QUERY_ALL"] = "QUERY_ALL";
15
- DOMOperation["GET_TEXT"] = "GET_TEXT";
16
- DOMOperation["GET_ATTRIBUTE"] = "GET_ATTRIBUTE";
17
- DOMOperation["GET_VALUE"] = "GET_VALUE";
18
- DOMOperation["HAS_CLASS"] = "HAS_CLASS";
19
- DOMOperation["GET_PAGE_URL"] = "GET_PAGE_URL";
20
- // Write operations
21
- DOMOperation["SET_TEXT"] = "SET_TEXT";
22
- DOMOperation["SET_ATTRIBUTE"] = "SET_ATTRIBUTE";
23
- DOMOperation["SET_VALUE"] = "SET_VALUE";
24
- DOMOperation["ADD_CLASS"] = "ADD_CLASS";
25
- DOMOperation["REMOVE_CLASS"] = "REMOVE_CLASS";
26
- DOMOperation["TOGGLE_CLASS"] = "TOGGLE_CLASS";
27
- DOMOperation["SET_STYLE"] = "SET_STYLE";
28
- // DOM manipulation
29
- DOMOperation["INSERT_HTML"] = "INSERT_HTML";
30
- DOMOperation["REMOVE_ELEMENT"] = "REMOVE_ELEMENT";
31
- // Element injection with event listeners
32
- DOMOperation["INJECT_ELEMENT"] = "INJECT_ELEMENT";
33
- DOMOperation["REMOVE_INJECTED_ELEMENT"] = "REMOVE_INJECTED_ELEMENT";
34
- // Form operations
35
- DOMOperation["GET_FORM_DATA"] = "GET_FORM_DATA";
36
- DOMOperation["START_FORM_CLICK_LISTENER"] = "START_FORM_CLICK_LISTENER";
37
- DOMOperation["STOP_FORM_CLICK_LISTENER"] = "STOP_FORM_CLICK_LISTENER";
38
- DOMOperation["SET_FORM_FIELD_VALUE"] = "SET_FORM_FIELD_VALUE";
39
- // Element click listener operations
40
- DOMOperation["START_ELEMENT_CLICK_LISTENER"] = "START_ELEMENT_CLICK_LISTENER";
41
- DOMOperation["STOP_ELEMENT_CLICK_LISTENER"] = "STOP_ELEMENT_CLICK_LISTENER";
42
- // Click operation
43
- DOMOperation["CLICK_ELEMENT"] = "CLICK_ELEMENT";
44
- // Element watching operations
45
- DOMOperation["WATCH_ELEMENT"] = "WATCH_ELEMENT";
46
- DOMOperation["UNWATCH_ELEMENT"] = "UNWATCH_ELEMENT";
47
- })(DOMOperation || (DOMOperation = {}));
48
- /**
49
- * Sidekick DOM API
50
- * Provides methods for iframe apps to interact with parent page DOM
51
- */
52
- export class SidekickDOMAPI {
53
- constructor() {
54
- this.pendingRequests = new Map();
55
- this.messageListener = null;
56
- this.requestCounter = 0;
57
- this.timeout = 5000; // 5 second timeout
58
- this.formClickCallback = null;
59
- this.workflowSavedCallback = null;
60
- this.triggerSavedCallback = null;
61
- this.elementClickCallbacks = new Map();
62
- this.elementSelectorClickCallbacks = new Map();
63
- this.elementChangeCallbacks = new Map();
64
- this.elementIdCounter = 0;
65
- this.watcherIdCounter = 0;
66
- this.initialize();
67
- }
68
- /**
69
- * Initialize the API and start listening for responses
70
- */
71
- initialize() {
72
- this.messageListener = (event) => {
73
- this.handleResponse(event);
74
- };
75
- window.addEventListener('message', this.messageListener);
76
- }
77
- /**
78
- * Cleanup
79
- */
80
- destroy() {
81
- if (this.messageListener) {
82
- window.removeEventListener('message', this.messageListener);
83
- this.messageListener = null;
84
- }
85
- this.pendingRequests.clear();
86
- }
87
- /**
88
- * Handle response from content script
89
- */
90
- handleResponse(event) {
91
- const data = event.data;
92
- // Only accept messages from parent (content script)
93
- if (event.source !== window.parent) {
94
- return;
95
- }
96
- // Handle form click events
97
- if (data?.type === 'SIDEKICK_FORM_CLICKED') {
98
- if (this.formClickCallback) {
99
- this.formClickCallback({
100
- formData: data.formData,
101
- clickedElement: data.clickedElement,
102
- });
103
- }
104
- return;
105
- }
106
- // Handle workflow saved events
107
- if (data?.type === 'WORKFLOW_SAVED') {
108
- if (this.workflowSavedCallback) {
109
- this.workflowSavedCallback(data.payload);
110
- }
111
- return;
112
- }
113
- // Handle trigger saved events
114
- if (data?.type === 'TRIGGER_SAVED') {
115
- if (this.triggerSavedCallback) {
116
- this.triggerSavedCallback(data.payload);
117
- }
118
- return;
119
- }
120
- // Handle element click events (for injected elements)
121
- if (data?.type === 'SIDEKICK_ELEMENT_CLICKED') {
122
- const callback = this.elementClickCallbacks.get(data.elementId);
123
- if (callback) {
124
- callback({
125
- elementId: data.elementId,
126
- event: data.event,
127
- });
128
- }
129
- return;
130
- }
131
- // Handle element selector click events (for element click listeners)
132
- if (data?.type === 'SIDEKICK_ELEMENT_SELECTOR_CLICKED') {
133
- const callback = this.elementSelectorClickCallbacks.get(data.listenerId);
134
- if (callback) {
135
- callback({
136
- element: data.element,
137
- event: data.event,
138
- });
139
- }
140
- return;
141
- }
142
- // Handle element change events (for element watchers)
143
- if (data?.type === 'SIDEKICK_ELEMENT_CHANGED') {
144
- const callback = this.elementChangeCallbacks.get(data.watcherId);
145
- if (callback) {
146
- callback({
147
- changeType: data.changeType,
148
- element: data.element,
149
- attributeName: data.attributeName,
150
- attributeValue: data.attributeValue,
151
- });
152
- }
153
- return;
154
- }
155
- // Handle normal DOM operation responses
156
- if (data?.type !== 'SIDEKICK_DOM_RESPONSE') {
157
- return;
158
- }
159
- const response = data;
160
- const pending = this.pendingRequests.get(response.id);
161
- if (!pending) {
162
- return;
163
- }
164
- this.pendingRequests.delete(response.id);
165
- if (response.success) {
166
- pending.resolve(response.data);
167
- }
168
- else {
169
- pending.reject(new Error(response.error || 'Unknown error'));
170
- }
171
- }
172
- /**
173
- * Send request to content script
174
- */
175
- sendRequest(operation, payload) {
176
- return new Promise((resolve, reject) => {
177
- const id = `dom-request-${++this.requestCounter}`;
178
- const request = {
179
- type: 'SIDEKICK_DOM_REQUEST',
180
- id,
181
- operation,
182
- payload,
183
- };
184
- // Store pending request
185
- this.pendingRequests.set(id, { resolve, reject });
186
- // Set timeout
187
- const timeoutId = setTimeout(() => {
188
- this.pendingRequests.delete(id);
189
- reject(new Error(`Request timeout: ${operation}`));
190
- }, this.timeout);
191
- // Clear timeout on resolve/reject
192
- const originalResolve = resolve;
193
- const originalReject = reject;
194
- this.pendingRequests.set(id, {
195
- resolve: (value) => {
196
- clearTimeout(timeoutId);
197
- originalResolve(value);
198
- },
199
- reject: (error) => {
200
- clearTimeout(timeoutId);
201
- originalReject(error);
202
- },
203
- });
204
- // Send to parent window (content script will intercept)
205
- window.parent.postMessage(request, '*');
206
- });
207
- }
208
- // ============================================================================
209
- // Read Operations
210
- // ============================================================================
211
- /**
212
- * Query a single element from parent page
213
- */
214
- async query(selector) {
215
- return this.sendRequest(DOMOperation.QUERY, { selector });
216
- }
217
- /**
218
- * Query all matching elements from parent page
219
- */
220
- async queryAll(selector) {
221
- return this.sendRequest(DOMOperation.QUERY_ALL, { selector });
222
- }
223
- /**
224
- * Get text content of an element
225
- */
226
- async getText(selector) {
227
- return this.sendRequest(DOMOperation.GET_TEXT, { selector });
228
- }
229
- /**
230
- * Get attribute value of an element
231
- */
232
- async getAttribute(selector, attribute) {
233
- return this.sendRequest(DOMOperation.GET_ATTRIBUTE, {
234
- selector,
235
- attribute
236
- });
237
- }
238
- /**
239
- * Get value of an input element
240
- */
241
- async getValue(selector) {
242
- return this.sendRequest(DOMOperation.GET_VALUE, { selector });
243
- }
244
- /**
245
- * Check if element has a class
246
- */
247
- async hasClass(selector, className) {
248
- return this.sendRequest(DOMOperation.HAS_CLASS, {
249
- selector,
250
- className
251
- });
252
- }
253
- /**
254
- * Get the current page URL
255
- * Returns the parent page URL (not the iframe URL)
256
- * @returns Promise with the current page URL
257
- */
258
- async getPageUrl() {
259
- return this.sendRequest(DOMOperation.GET_PAGE_URL, {});
260
- }
261
- // ============================================================================
262
- // Write Operations
263
- // ============================================================================
264
- /**
265
- * Set text content of an element
266
- */
267
- async setText(selector, text) {
268
- return this.sendRequest(DOMOperation.SET_TEXT, { selector, text });
269
- }
270
- /**
271
- * Set attribute value of an element
272
- */
273
- async setAttribute(selector, attribute, value) {
274
- return this.sendRequest(DOMOperation.SET_ATTRIBUTE, {
275
- selector,
276
- attribute,
277
- value
278
- });
279
- }
280
- /**
281
- * Set value of an input element
282
- */
283
- async setValue(selector, value) {
284
- return this.sendRequest(DOMOperation.SET_VALUE, { selector, value });
285
- }
286
- /**
287
- * Add a class to an element
288
- */
289
- async addClass(selector, className) {
290
- return this.sendRequest(DOMOperation.ADD_CLASS, { selector, className });
291
- }
292
- /**
293
- * Remove a class from an element
294
- */
295
- async removeClass(selector, className) {
296
- return this.sendRequest(DOMOperation.REMOVE_CLASS, { selector, className });
297
- }
298
- /**
299
- * Toggle a class on an element
300
- */
301
- async toggleClass(selector, className) {
302
- return this.sendRequest(DOMOperation.TOGGLE_CLASS, { selector, className });
303
- }
304
- /**
305
- * Set a CSS style property on an element
306
- */
307
- async setStyle(selector, property, value) {
308
- return this.sendRequest(DOMOperation.SET_STYLE, {
309
- selector,
310
- property,
311
- value
312
- });
313
- }
314
- /**
315
- * Insert HTML relative to an element
316
- */
317
- async insertHTML(selector, position, html) {
318
- return this.sendRequest(DOMOperation.INSERT_HTML, {
319
- selector,
320
- position,
321
- html
322
- });
323
- }
324
- /**
325
- * Remove an element from the DOM
326
- */
327
- async removeElement(selector) {
328
- return this.sendRequest(DOMOperation.REMOVE_ELEMENT, { selector });
329
- }
330
- /**
331
- * Click an element on the parent page
332
- *
333
- * @param selector - CSS selector for the element to click
334
- * @returns Promise that resolves when the click is complete
335
- *
336
- * @example
337
- * // Click a button
338
- * await sidekickDOM.clickElement('button#submit');
339
- *
340
- * // Click a link
341
- * await sidekickDOM.clickElement('a.nav-item[href="/dashboard"]');
342
- */
343
- async clickElement(selector) {
344
- return this.sendRequest(DOMOperation.CLICK_ELEMENT, { selector });
345
- }
346
- // ============================================================================
347
- // Form Operations
348
- // ============================================================================
349
- /**
350
- * Get form data from a form or an element inside a form
351
- * @param selector - CSS selector for the form or an element inside the form
352
- * @returns Promise with complete form data including all fields
353
- */
354
- async getFormData(selector) {
355
- return this.sendRequest(DOMOperation.GET_FORM_DATA, { selector });
356
- }
357
- /**
358
- * Start listening for clicks on form elements
359
- * When a form element is clicked, the callback will be invoked with the form data
360
- * @param callback - Function to call when a form element is clicked
361
- */
362
- async startFormClickListener(callback) {
363
- this.formClickCallback = callback;
364
- return this.sendRequest(DOMOperation.START_FORM_CLICK_LISTENER, {});
365
- }
366
- /**
367
- * Stop listening for clicks on form elements
368
- */
369
- async stopFormClickListener() {
370
- this.formClickCallback = null;
371
- return this.sendRequest(DOMOperation.STOP_FORM_CLICK_LISTENER, {});
372
- }
373
- /**
374
- * Start listening for clicks on elements matching a CSS selector
375
- * When an element is clicked, the callback will be invoked with element data
376
- *
377
- * @param selector - CSS selector for elements to listen to (e.g., 'div.sortable-item[role="button"]')
378
- * @param callback - Function to call when a matching element is clicked
379
- * @param options - Optional configuration
380
- * @param options.listenerId - Unique ID for this listener (auto-generated if not provided)
381
- * @returns Promise<string> - The listener ID (for cleanup with stopElementClickListener)
382
- *
383
- * @example
384
- * // Listen to tab buttons
385
- * const listenerId = await sidekickDOM.startElementClickListener(
386
- * 'div.sortable-item[role="button"]',
387
- * (data) => {
388
- * console.log('Tab clicked:', data.element.textContent);
389
- * console.log('Element:', data.element);
390
- * }
391
- * );
392
- *
393
- * // Later, stop listening
394
- * await sidekickDOM.stopElementClickListener(listenerId);
395
- */
396
- async startElementClickListener(selector, callback, options) {
397
- const listenerId = options?.listenerId || `listener-${Date.now()}-${Math.random()}`;
398
- // Store callback
399
- this.elementSelectorClickCallbacks.set(listenerId, callback);
400
- // Send request to content script
401
- await this.sendRequest(DOMOperation.START_ELEMENT_CLICK_LISTENER, {
402
- selector,
403
- listenerId,
404
- });
405
- return listenerId;
406
- }
407
- /**
408
- * Stop listening for clicks on elements
409
- *
410
- * @param listenerId - The ID of the listener to stop (returned from startElementClickListener)
411
- *
412
- * @example
413
- * const listenerId = await sidekickDOM.startElementClickListener('button', callback);
414
- * // ... later ...
415
- * await sidekickDOM.stopElementClickListener(listenerId);
416
- */
417
- async stopElementClickListener(listenerId) {
418
- // Remove callback
419
- this.elementSelectorClickCallbacks.delete(listenerId);
420
- // Send request to content script
421
- return this.sendRequest(DOMOperation.STOP_ELEMENT_CLICK_LISTENER, {
422
- listenerId,
423
- });
424
- }
425
- /**
426
- * Set the value of a form field
427
- * Handles all input types (text, checkbox, radio, select, textarea, etc.)
428
- * Automatically triggers validation events
429
- *
430
- * @param selector - CSS selector for the form field
431
- * @param value - Value to set (string for text inputs, boolean for checkboxes, array for multi-selects)
432
- * @param scrollIntoView - Optional: if true, scrolls element to center of viewport before setting value (default: false)
433
- *
434
- * @example
435
- * // Text input
436
- * await sidekickDOM.setFormFieldValue('input[name="email"]', 'user@example.com');
437
- *
438
- * // Checkbox with scroll
439
- * await sidekickDOM.setFormFieldValue('input[name="terms"]', true, true);
440
- *
441
- * // Radio button (set to the value of the radio to select)
442
- * await sidekickDOM.setFormFieldValue('input[name="gender"][value="female"]', 'female');
443
- *
444
- * // Select
445
- * await sidekickDOM.setFormFieldValue('select[name="country"]', 'US');
446
- *
447
- * // Multi-select with scroll to make it visible
448
- * await sidekickDOM.setFormFieldValue('select[name="interests"]', ['sports', 'music'], true);
449
- */
450
- async setFormFieldValue(selector, value, scrollIntoView = false) {
451
- return this.sendRequest(DOMOperation.SET_FORM_FIELD_VALUE, {
452
- selector,
453
- value,
454
- scrollIntoView
455
- });
456
- }
457
- // ============================================================================
458
- // Event Listeners
459
- // ============================================================================
460
- /**
461
- * Register a callback for when a workflow is saved
462
- * @param callback - Function to call when a workflow is saved from the content script
463
- */
464
- onWorkflowSaved(callback) {
465
- this.workflowSavedCallback = callback;
466
- }
467
- /**
468
- * Remove the workflow saved callback
469
- */
470
- offWorkflowSaved() {
471
- this.workflowSavedCallback = null;
472
- }
473
- /**
474
- * Register a callback for when a trigger is saved
475
- * @param callback - Function to call when a trigger is saved from the content script
476
- */
477
- onTriggerSaved(callback) {
478
- this.triggerSavedCallback = callback;
479
- }
480
- /**
481
- * Remove the trigger saved callback
482
- */
483
- offTriggerSaved() {
484
- this.triggerSavedCallback = null;
485
- }
486
- // ============================================================================
487
- // Element Injection Operations
488
- // ============================================================================
489
- /**
490
- * Inject an element onto the parent page with optional click event listener
491
- *
492
- * @param targetSelector - CSS selector for the element to inject relative to
493
- * @param position - Position relative to target ('beforebegin', 'afterbegin', 'beforeend', 'afterend')
494
- * @param html - HTML string to inject (will be sanitized)
495
- * @param options - Optional configuration
496
- * @param options.onClick - Callback function to invoke when element is clicked
497
- * @param options.elementId - Custom element ID (auto-generated if not provided)
498
- * @returns Promise with the element ID
499
- *
500
- * @example
501
- * // Inject a button with click handler
502
- * const elementId = await sidekickDOM.injectElement(
503
- * 'body',
504
- * 'beforeend',
505
- * '<button class="my-button">Click Me</button>',
506
- * {
507
- * onClick: (data) => {
508
- * console.log('Button clicked!', data);
509
- * }
510
- * }
511
- * );
512
- *
513
- * // Later, remove the element
514
- * await sidekickDOM.removeInjectedElement(elementId);
515
- */
516
- async injectElement(targetSelector, position, html, options) {
517
- // Generate element ID if not provided
518
- const elementId = options?.elementId || `sidekick-injected-${++this.elementIdCounter}`;
519
- // Store click callback if provided
520
- if (options?.onClick) {
521
- this.elementClickCallbacks.set(elementId, options.onClick);
522
- }
523
- // Send injection request
524
- const result = await this.sendRequest(DOMOperation.INJECT_ELEMENT, {
525
- targetSelector,
526
- position,
527
- html,
528
- elementId,
529
- onClick: !!options?.onClick,
530
- });
531
- return result.elementId;
532
- }
533
- /**
534
- * Remove an injected element from the parent page
535
- *
536
- * @param elementId - ID of the element to remove (returned from injectElement)
537
- *
538
- * @example
539
- * await sidekickDOM.removeInjectedElement('sidekick-injected-1');
540
- */
541
- async removeInjectedElement(elementId) {
542
- // Remove click callback if it exists
543
- this.elementClickCallbacks.delete(elementId);
544
- // Send removal request
545
- return this.sendRequest(DOMOperation.REMOVE_INJECTED_ELEMENT, {
546
- elementId,
547
- });
548
- }
549
- // ============================================================================
550
- // Element Watching Operations
551
- // ============================================================================
552
- /**
553
- * Watch an element for changes (attributes, removal, children)
554
- *
555
- * Sets up MutationObservers to monitor:
556
- * - Attribute changes on the element
557
- * - Element removal from DOM
558
- * - Child node changes (optional)
559
- *
560
- * @param selector - CSS selector for the element to watch
561
- * @param callback - Function to call when element changes
562
- * @param options - Optional configuration
563
- * @param options.watchAttributes - Watch for attribute changes (default: true)
564
- * @param options.watchChildren - Watch for child node changes (default: false)
565
- * @param options.attributeFilter - Optional array of specific attributes to watch
566
- * @returns Promise with the watcher ID (use to stop watching)
567
- *
568
- * @example
569
- * // Watch for attribute changes
570
- * const watcherId = await sidekickDOM.watchElement(
571
- * '#my-element',
572
- * (data) => {
573
- * if (data.changeType === 'attribute') {
574
- * console.log(`Attribute ${data.attributeName} changed to: ${data.attributeValue}`);
575
- * } else if (data.changeType === 'removed') {
576
- * console.log('Element was removed from DOM');
577
- * }
578
- * },
579
- * {
580
- * watchAttributes: true,
581
- * attributeFilter: ['class', 'data-status'] // Only watch specific attributes
582
- * }
583
- * );
584
- *
585
- * // Later, stop watching
586
- * await sidekickDOM.unwatchElement(watcherId);
587
- */
588
- async watchElement(selector, callback, options) {
589
- // Generate watcher ID
590
- const watcherId = `sidekick-watcher-${++this.watcherIdCounter}`;
591
- // Store callback
592
- this.elementChangeCallbacks.set(watcherId, callback);
593
- // Send watch request
594
- await this.sendRequest(DOMOperation.WATCH_ELEMENT, {
595
- selector,
596
- watcherId,
597
- watchAttributes: options?.watchAttributes,
598
- watchChildren: options?.watchChildren,
599
- attributeFilter: options?.attributeFilter,
600
- });
601
- return watcherId;
602
- }
603
- /**
604
- * Stop watching an element
605
- *
606
- * @param watcherId - ID of the watcher to stop (returned from watchElement)
607
- *
608
- * @example
609
- * await sidekickDOM.unwatchElement('sidekick-watcher-1');
610
- */
611
- async unwatchElement(watcherId) {
612
- // Remove callback
613
- this.elementChangeCallbacks.delete(watcherId);
614
- // Send unwatch request
615
- return this.sendRequest(DOMOperation.UNWATCH_ELEMENT, {
616
- watcherId,
617
- });
618
- }
619
- }
620
- export default new SidekickDOMAPI();