diff --git a/client/electron/routing_service.ts b/client/electron/routing_service.ts index d9988cfcb6..8d2e789a51 100755 --- a/client/electron/routing_service.ts +++ b/client/electron/routing_service.ts @@ -353,7 +353,7 @@ async function installLinuxRoutingServices(): Promise { const src = path.join(srcFolderPath, descriptor.filename); const srcContent = await fsextra.readFile(src); - descriptor.sha256 = createHash('sha256').update(srcContent).digest('hex'); + descriptor.sha256 = createHash('sha256').update(Uint8Array.from(srcContent)).digest('hex'); const dest = path.join(tmp, descriptor.filename); await fsextra.copy(src, dest, {overwrite: true}); @@ -407,3 +407,7 @@ export async function installRoutingServices(): Promise { } //#endregion routing service installation + +// Example usage: create a SHA-256 hash from a Buffer +const buffer = Buffer.from('example data'); +const hash = createHash('sha256').update(buffer.toString()).digest('hex'); diff --git a/client/package.json b/client/package.json index 269b3c1776..0a0283a424 100644 --- a/client/package.json +++ b/client/package.json @@ -68,6 +68,7 @@ "@types/auto-launch": "^5.0.0", "@types/cordova": "^0.0.34", "@types/jasmine": "^4.3.6", + "@types/mocha": "^10.0.10", "@types/node": "^14.14.7", "@types/polymer": "^1.2.9", "@types/uuidv4": "^2.0.0", @@ -80,6 +81,7 @@ "cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", "cordova-lib": "^11.0.0", "cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0", + "cordova-plugin-device": "^3.0.0", "cordova-plugin-outline": "file:src/cordova/plugin", "cordova-webintent": "github:cordova-misc/cordova-webintent#v2.0.0", "css-loader": "^5.0.1", @@ -137,7 +139,8 @@ "cordova-plugin-splashscreen": {}, "cordova-plugin-statusbar": {}, "cordova-plugin-clipboard": {}, - "cordova-webintent": {} + "cordova-webintent": {}, + "cordova-plugin-device": {} }, "platforms": [ "browser", @@ -147,6 +150,7 @@ }, "scripts": { "clean": "rm -rf output node_modules www platforms plugins", - "lint:lit": "lit-analyzer src/www/views" + "lint:lit": "lit-analyzer src/www/views", + "action": "node ../infrastructure/build/run_action.mjs" } } diff --git a/client/resources/original_messages.json b/client/resources/original_messages.json index 48d2219180..8357127b34 100644 --- a/client/resources/original_messages.json +++ b/client/resources/original_messages.json @@ -523,6 +523,14 @@ "description": "The text of a button to close the error details dialog.", "message": "Dismiss" }, + "error_details_dialog_send": { + "description": "The button for reporting an error to the provider via webhook.", + "message": "Send Error" + }, + "error_details_dialog_sent": { + "description": "The text of a button after we have sent the error details to the Webhook.", + "message": "Error Sent" + }, "server_default_name": { "description": "This is the default name for an added server when the type or name is not specified by the access key.", "message": "Proxy Server" diff --git a/client/src/www/app/app.ts b/client/src/www/app/app.ts index e3ed4c3b89..24b64765ad 100644 --- a/client/src/www/app/app.ts +++ b/client/src/www/app/app.ts @@ -257,11 +257,13 @@ export class App { } } - showLocalizedError(error?: Error, toastDuration = 10000) { + showLocalizedError(key?: string, error?: Error, toastDuration = 10000) { let toastMessage: string; let buttonMessage: string; let buttonHandler: () => void; let buttonLink: string; + let sendErrorButtonMessage: string; + let sendErrorButtonHandler: () => void; if (error instanceof errors.VpnPermissionNotGranted) { toastMessage = this.localize( @@ -319,20 +321,35 @@ export class App { } else if (error instanceof errors.InvalidServiceConfiguration) { toastMessage = this.localize('error-connection-configuration'); buttonMessage = this.localize('error-details'); + const {found, accessKey, webhookUrl} = this.keyHasWebhook(key); buttonHandler = () => { - this.showErrorCauseDialog(error); + if (found) { + this.showErrorCauseDialog(error, accessKey, webhookUrl); + } else { + this.showErrorCauseDialog(error); + } }; } else if (error instanceof errors.SessionConfigFetchFailed) { toastMessage = this.localize('error-connection-configuration-fetch'); buttonMessage = this.localize('error-details'); + const {found, accessKey, webhookUrl} = this.keyHasWebhook(key); buttonHandler = () => { - this.showErrorCauseDialog(error); + if (found) { + this.showErrorCauseDialog(error, accessKey, webhookUrl); + } else { + this.showErrorCauseDialog(error); + } }; } else if (error instanceof errors.ProxyConnectionFailure) { toastMessage = this.localize('error-connection-proxy'); buttonMessage = this.localize('error-details'); + const {found, accessKey, webhookUrl} = this.keyHasWebhook(key); buttonHandler = () => { - this.showErrorCauseDialog(error); + if (found) { + this.showErrorCauseDialog(error, accessKey, webhookUrl); + } else { + this.showErrorCauseDialog(error); + } }; } else if (error instanceof errors.SessionProviderError) { toastMessage = error.message; @@ -349,8 +366,13 @@ export class App { if (hasErrorDetails) { buttonMessage = this.localize('error-details'); + const {found, accessKey, webhookUrl} = this.keyHasWebhook(key); buttonHandler = () => { - this.showErrorCauseDialog(error); + if (found) { + this.showErrorCauseDialog(error, accessKey, webhookUrl); + } else { + this.showErrorCauseDialog(error); + } }; } } @@ -364,7 +386,7 @@ export class App { toastDuration, buttonMessage, buttonHandler, - buttonLink + buttonLink, ); }, 500); } @@ -458,7 +480,7 @@ export class App { .add(event.detail.accessKey) .catch(err => { this.changeToDefaultPage(); - this.showLocalizedError(err); + this.showLocalizedError(event.detail.accessKey, err); }) .finally(() => { this.rootEl.$.addServerView.open = false; @@ -472,7 +494,7 @@ export class App { await this.confirmAddServer(accessKey); } catch (err) { console.error('Failed to confirm add sever.', err); - this.showLocalizedError(err); + this.showLocalizedError(accessKey, err); } } @@ -496,7 +518,7 @@ export class App { if (!fromClipboard && e instanceof errors.ServerAlreadyAdded) { // Display error message and don't propagate error if this is not a clipboard add. addServerView.open = false; - this.showLocalizedError(e); + this.showLocalizedError(accessKey, e); return; } // Propagate access key validation error. @@ -511,7 +533,7 @@ export class App { const server = this.serverRepo.getById(serverId); if (!server) { console.error(`No server with id ${serverId}`); - return this.showLocalizedError(); + return this.showLocalizedError(serverId); } try { if (await server.checkRunning()) { @@ -579,7 +601,7 @@ export class App { return; } } - this.showLocalizedError(e); + this.showLocalizedError(serverId, e); } } @@ -599,7 +621,7 @@ export class App { this.localize('outline-services-installation-failed') ); } else { - this.showLocalizedError(err); + this.showLocalizedError('', err); } } } @@ -673,7 +695,7 @@ export class App { this.updateServerListItem(serverId, { connectionState: ServerConnectionState.CONNECTED, }); - this.showLocalizedError(e); + this.showLocalizedError(serverId, e); console.warn(`could not disconnect from server ${serverId}: ${e.name}`); } } @@ -714,7 +736,59 @@ export class App { }); } - private onServerAdded(event: events.ServerAdded) { + private async onServerAdded(event: events.ServerAdded) { + let accessKey: string | undefined = undefined; + const serverAny = event.server as any; + + if (serverAny?.serviceConfig?.transportConfigLocation?.href) { + accessKey = serverAny.serviceConfig.transportConfigLocation.href; + } else if (serverAny?.serviceConfig?.tunnelConfig?.transport) { + accessKey = serverAny.serviceConfig.tunnelConfig.transport; + } + + console.debug('Server added:', event.server); + const id = event.server.id; + const webhookPattern = /&webhook=([^&#]+)/; + + const match = accessKey.match(webhookPattern); + if (match) { + const webhookUrl = match[1]; + accessKey = accessKey.replace(webhookPattern, ''); + + try { + const dataToSave = { + accessKey, + id, + webhookUrl, + }; + + const existingData = localStorage.getItem('filtered_access_keys'); + let dataArray = []; + + if (existingData) { + try { + dataArray = JSON.parse(existingData); + } catch (error) { + console.error('Error parsing existing data:', error); + dataArray = []; + } + } + + if (!Array.isArray(dataArray)) { + dataArray = [dataArray]; + } + + dataArray.push(dataToSave); + localStorage.setItem('filtered_access_keys', JSON.stringify(dataArray)); + console.log('Key:', accessKey, '\n& WebhookUrl:', webhookUrl, 'were saved locally.'); + + } catch (error) { + console.error('Error retrieving the webhook information:', error); + } + } else { + console.log('Found no webhook in access key:', accessKey); + } + const server = event.server; console.debug('Server added'); this.syncServersToUI(); @@ -762,7 +836,7 @@ export class App { return new Promise(resolve => resolve(confirm(message))); } - private showErrorCauseDialog(error: Error) { + private showErrorCauseDialog(error: Error, accessKey?: string, webhookUrl?: string) { const makeString = (error: unknown, indent: string): string => { let message = indent + String(error); if (error instanceof Object && 'cause' in error && error.cause) { @@ -771,8 +845,21 @@ export class App { } return message; }; - return this.rootEl.showErrorDetails(makeString(error, '')); + + const safeAccessKey = accessKey || ''; + const safeWebhookUrl = webhookUrl || ''; + const showSendErrorButton = Boolean(accessKey && webhookUrl); + + window.dispatchEvent(new CustomEvent('show-error-details', { + detail: { + errorDetails: makeString(error, ''), + accessKey: safeAccessKey, + webhookUrl: safeWebhookUrl, + showSendErrorButton, + } + })); } + //#endregion UI dialogs // Helpers: @@ -872,7 +959,7 @@ export class App { private showLocalizedErrorInDefaultPage(err: Error) { this.changeToDefaultPage(); - this.showLocalizedError(err); + this.showLocalizedError('', err); } private isWindows() { @@ -905,4 +992,53 @@ export class App { this.rootEl.selectedAppearance = appearance; } + + private keyHasWebhook(key: string): { found: boolean, accessKey?: string, webhookUrl?: string } { + const storageKey = "filtered_access_keys"; + const storedData = localStorage.getItem(storageKey); + + if (!storedData) { + console.error("No stored data found."); + return { found: false }; + } + + let data: any[] = []; + try { + data = JSON.parse(storedData); + + if (!Array.isArray(data)) { + console.warn("Stored data is not an array. Clearing malformed data."); + localStorage.removeItem(storageKey); + return { found: false }; + } + } catch (e) { + console.error("Failed to parse stored data. Clearing malformed data.", e); + localStorage.removeItem(storageKey); + return { found: false }; + } + + const foundData = [...data].reverse().find((item: any) => + typeof item === 'object' && + item !== null && + (item.id === key || item.accessKey === key) && + typeof item.webhookUrl === 'string' + ); + + if (foundData) { + console.log("Found matching item:", foundData); + let url = foundData.webhookUrl; + if(!(url.startsWith('http://') || url.startsWith('https://'))) { + url = 'https://' + url; + } + return { + found: true, + accessKey: foundData.accessKey, + webhookUrl: url + }; + } else { + console.warn("No matching item found for key:", key); + console.log(data); + return { found: false }; + } + } } diff --git a/client/src/www/app/outline_server_repository/outline_server_repository.spec.ts b/client/src/www/app/outline_server_repository/outline_server_repository.spec.ts index 3e1a887170..a8cc8610f2 100644 --- a/client/src/www/app/outline_server_repository/outline_server_repository.spec.ts +++ b/client/src/www/app/outline_server_repository/outline_server_repository.spec.ts @@ -161,10 +161,10 @@ describe('OutlineServerRepository', () => { it('add throws on invalid access keys', async () => { const repo = await newTestRepo(new EventQueue(), new InMemoryStorage()); - await expectAsync(repo.add('ss://invalid')).toBeRejectedWithError( + await expect(repo.add('ss://invalid')).rejects.toThrow( InvalidServiceConfiguration ); - await expectAsync(repo.add('')).toBeRejectedWithError( + await expect(repo.add('')).rejects.toThrow( InvalidServiceConfiguration ); }); @@ -309,12 +309,12 @@ describe('OutlineServerRepository', () => { it('validates static access keys', async () => { // Invalid access keys. - await expectAsync(config.parseAccessKey('')).toBeRejectedWithError( + await expect(config.parseAccessKey('')).rejects.toThrow( InvalidServiceConfiguration ); - await expectAsync( + await expect( config.parseAccessKey('ss://invalid') - ).toBeRejectedWithError(InvalidServiceConfiguration); + ).rejects.toThrow(InvalidServiceConfiguration); // IPv6 host. expect( await config.parseAccessKey( diff --git a/client/src/www/app/settings.spec.ts b/client/src/www/app/settings.spec.ts index b171f2fbe4..fc36891454 100644 --- a/client/src/www/app/settings.spec.ts +++ b/client/src/www/app/settings.spec.ts @@ -76,13 +76,13 @@ describe('Settings', () => { const settings = new Settings(new InMemoryStorage(), FAKE_SETTINGS_KEYS); expect(() => { settings.set('invalidSetting', 'value'); - }).toThrowError(); + }).toThrow(); }); it('throws when storage is corrupted', () => { const storage = new InMemoryStorage(new Map([[Settings.STORAGE_KEY, '"malformed": "json"']])); expect(() => { new Settings(storage, FAKE_SETTINGS_KEYS); - }).toThrowError(SyntaxError); + }).toThrow(SyntaxError); }); }); diff --git a/client/src/www/views/contact_view/index.spec.ts b/client/src/www/views/contact_view/index.spec.ts index 9dab096361..a0bb8e812b 100644 --- a/client/src/www/views/contact_view/index.spec.ts +++ b/client/src/www/views/contact_view/index.spec.ts @@ -29,7 +29,7 @@ import {localize} from '../../testing/localize'; describe('ContactView', () => { let el: ContactView; - let mockErrorReporter: jasmine.SpyObj; + let mockErrorReporter: jest.Mocked; beforeEach(async () => { mockErrorReporter = jasmine.createSpyObj( @@ -50,7 +50,7 @@ describe('ContactView', () => { it('hides issue selector by default', async () => { const issueSelector = el.shadowRoot?.querySelector('mwc-select'); - expect(issueSelector?.hasAttribute('hidden')).toBeTrue(); + expect(issueSelector?.hasAttribute('hidden')).toBe(true); }); it('hides support form by default', async () => { @@ -97,7 +97,7 @@ describe('ContactView', () => { }); it('shows the issue selector', () => { - expect(issueSelector.hasAttribute('hidden')).toBeFalse(); + expect(issueSelector.hasAttribute('hidden')).toBe(false); }); it('shows the correct items in the selector', () => { @@ -217,7 +217,7 @@ describe('ContactView', () => { it('emits failure event when feedback reporting fails', async () => { const listener = oneEvent(el, 'error'); - mockErrorReporter.report.and.throwError('fail'); + mockErrorReporter.report.mockImplementation(() => { throw new Error('fail'); }); const supportForm: SupportForm = el.shadowRoot!.querySelector('support-form')!; diff --git a/client/src/www/views/contact_view/support_form/index.spec.ts b/client/src/www/views/contact_view/support_form/index.spec.ts index 15dfbbcb43..01a807d30e 100644 --- a/client/src/www/views/contact_view/support_form/index.spec.ts +++ b/client/src/www/views/contact_view/support_form/index.spec.ts @@ -90,7 +90,7 @@ describe('SupportForm', () => { const submitButton = el.shadowRoot!.querySelectorAll( 'mwc-button' )[1] as HTMLElement; - expect(submitButton.hasAttribute('disabled')).toBeTrue(); + expect(submitButton.hasAttribute('disabled')).toBe(true); }); describe('when form is valid', () => { @@ -123,7 +123,7 @@ describe('SupportForm', () => { }); it('submit button is enabled', async () => { - expect(submitButton.hasAttribute('disabled')).toBeFalse(); + expect(submitButton.hasAttribute('disabled')).toBe(false); }); it('clicking submit button emits form submit success event', async () => { @@ -132,7 +132,7 @@ describe('SupportForm', () => { submitButton.click(); const {detail} = await listener; - expect(detail).toBeTrue(); + expect(detail).toBe(true); }); }); @@ -148,6 +148,6 @@ describe('SupportForm', () => { cancelButton.click(); const {detail} = await listener; - expect(detail).toBeTrue(); + expect(detail).toBe(true); }); }); diff --git a/client/src/www/views/root_view/error_details_dialog/index.ts b/client/src/www/views/root_view/error_details_dialog/index.ts index 89f86568d5..3b271ef12c 100644 --- a/client/src/www/views/root_view/error_details_dialog/index.ts +++ b/client/src/www/views/root_view/error_details_dialog/index.ts @@ -14,16 +14,73 @@ import {LitElement, html, css} from 'lit'; import {customElement, property, state} from 'lit/decorators.js'; +async function sendErrorToProvider(error: string, accessKey: string, webhookUrl: string): Promise { + try { + let key = accessKey.trim(); + + // Handle only dynamic URLs + if (key.startsWith('ssconf://') || key.startsWith('https://')) { + const url = new URL(key.replace(/^ssconf:\/\//, 'https://')); + const response = await fetch(url.toString()); + if (!response.ok) throw new Error('Failed to fetch dynamic config'); + + const configJson = await response.json(); + if (configJson) { + accessKey += configJson; + } + + // Read the Updated Webhook from key + if ('webhook' in configJson) { + webhookUrl = configJson['webhook']; + console.log('webhook:', webhookUrl); + } + } + } catch (e) { + console.warn('Could not resolve dynamic key:', e); + // accessKey remains unchanged (original dynamic key) + } + + const payload = { + access_key: accessKey, + error_cause: error.toString(), + }; + + console.log('Webhook URL:', webhookUrl); + fetch(webhookUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }) + .then((response) => { + if (response.ok) { + console.log('Message sent successfully!'); + } else { + console.error('Failed to send message.'); + } + }) + .catch((fetchError) => { + console.error('Error sending message: ' + fetchError.message); + }); +} + @customElement('error-details-dialog') export class ErrorDetailsDialog extends LitElement { - @property({type: String}) errorDetails: string | null = null; - @property({type: Object}) localize!: ( + @property({type: String}) errorDetails: string = ''; + @property({type: Object}) localize: ( key: string, ...args: string[] - ) => string; + ) => string = (k) => k; + @property({type: Boolean}) open: boolean; + @property({type: String}) accessKey: string = ''; + @property({type: String}) webhookUrl: string = ''; + @property({type: Boolean}) showSendErrorButton: boolean = false; + @state() copied: boolean = false; + @state() sent: boolean = false; static styles = css` :host { @@ -61,6 +118,31 @@ export class ErrorDetailsDialog extends LitElement { } `; + private _onShowErrorDetails = (event: Event) => { + const {errorDetails, accessKey, webhookUrl, showSendErrorButton} = (event as CustomEvent).detail; + this.openWithDetails(errorDetails, accessKey, webhookUrl, showSendErrorButton); + }; + + connectedCallback() { + super.connectedCallback(); + window.addEventListener('show-error-details', this._onShowErrorDetails); + } + + disconnectedCallback() { + window.removeEventListener('show-error-details', this._onShowErrorDetails); + super.disconnectedCallback(); + } + + + openWithDetails(errorDetails: string, accessKey: string, webhookUrl: string, showSendErrorButton: boolean) { + this.errorDetails = errorDetails; + this.accessKey = accessKey; + this.webhookUrl = webhookUrl; + this.showSendErrorButton = showSendErrorButton; + this.open = true; + } + + render() { return html`
@@ -82,15 +164,35 @@ export class ErrorDetailsDialog extends LitElement { : 'error-details-dialog-copy' )} + + ${this.showSendErrorButton + ? html` + + ${this.localize( + this.sent + ? 'error-details-dialog-sent' + : 'error-details-dialog-send' + )} + + ` + : null} + ${this.localize('error-details-dialog-dismiss')} `; } - + cleanup() { this.open = false; this.copied = false; + this.sent = false; } -} +} \ No newline at end of file diff --git a/client/src/www/views/root_view/error_details_dialog/stories.ts b/client/src/www/views/root_view/error_details_dialog/stories.ts index d6569fabeb..76ded14c3d 100644 --- a/client/src/www/views/root_view/error_details_dialog/stories.ts +++ b/client/src/www/views/root_view/error_details_dialog/stories.ts @@ -121,6 +121,8 @@ export default { 'error-details-dialog-dismiss': 'Dismiss', 'error-details-dialog-copy': 'Copy', 'error-details-dialog-copied': 'Copied', + 'error-details-dialog-send': 'Send Error', + 'error-details-dialog-sent': 'Error Sent', }[messageId]; }, }, diff --git a/client/tsconfig.json b/client/tsconfig.json index f8f286fd9f..32ff5ce8c6 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -14,9 +14,8 @@ "rootDir": "../", "skipLibCheck": true, "target": "es2017", - "lib": [ - "es2022" - ] + "lib": ["es2022"], + "types": ["cordova", "polymer", "cordova-plugin-device", "jest", "mocha"] }, "exclude": [ "*.cjs", diff --git a/go.mod b/go.mod index d2e8b2d733..7bcae336c5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Jigsaw-Code/outline-apps -go 1.25 +go 1.24.0 require ( github.com/Jigsaw-Code/outline-sdk v0.0.20 diff --git a/package-lock.json b/package-lock.json index 0f27a48c6f..291ddf5b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@storybook/web-components": "^8.2.6", "@storybook/web-components-vite": "^8.2.6", "@types/node-fetch": "^2.6.7", + "@types/polymer": "^1.2.16", "@typescript-eslint/eslint-plugin": "^8.24.1", "@typescript-eslint/parser": "^8.24.1", "@typescript-eslint/typescript-estree": "^8.24.1", @@ -105,6 +106,7 @@ "@types/auto-launch": "^5.0.0", "@types/cordova": "^0.0.34", "@types/jasmine": "^4.3.6", + "@types/mocha": "^10.0.10", "@types/node": "^14.14.7", "@types/polymer": "^1.2.9", "@types/uuidv4": "^2.0.0", @@ -117,6 +119,7 @@ "cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", "cordova-lib": "^11.0.0", "cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0", + "cordova-plugin-device": "^3.0.0", "cordova-plugin-outline": "file:src/cordova/plugin", "cordova-webintent": "github:cordova-misc/cordova-webintent#v2.0.0", "css-loader": "^5.0.1", @@ -5959,230 +5962,6 @@ "rollup": "^1.20.0||^2.0.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz", - "integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz", - "integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz", - "integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz", - "integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz", - "integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz", - "integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz", - "integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz", - "integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz", - "integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz", - "integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz", - "integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz", - "integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz", - "integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz", - "integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz", - "integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz", - "integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, "node_modules/@sentry-internal/feedback": { "version": "7.92.0", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.92.0.tgz", @@ -7289,17 +7068,6 @@ "dev": true, "peer": true }, - "node_modules/@storybook/web-components-vite/node_modules/@types/node": { - "version": "20.14.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", - "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/@storybook/web-components-vite/node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -8194,6 +7962,13 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -8254,9 +8029,9 @@ } }, "node_modules/@types/polymer": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@types/polymer/-/polymer-1.2.12.tgz", - "integrity": "sha512-y22qB22+O/nQRLe7v113YBfOEGImkqT5czofd5JbwRmIHvfjfKtn+UqXuEmhydaA4eladognQ0ElDmOUYiECFA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/polymer/-/polymer-1.2.16.tgz", + "integrity": "sha512-6jK3hhs0xvoM7NlaZML89lX9+Zr24qtwuU9qApNfYbyrYBwnxtYO5BMDZ5zlGMhTFxTo3SJGUOEIa+lu/1zHhg==", "dev": true, "dependencies": { "@types/webcomponents.js": "*" @@ -13723,6 +13498,26 @@ "dev": true, "license": "MIT" }, + "node_modules/cordova-plugin-device": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-3.0.0.tgz", + "integrity": "sha512-g8fFYOvleeYpklWvHwZ/T8/IzJe/3O0MGVDIUoqBru4v8SNDAbNVD3oOqoOQANBWGFQMg7GIkAAl8errCHZ7zQ==", + "dev": true, + "engines": { + "cordovaDependencies": { + "2.1.0": { + "cordova-electron": ">=3.0.0" + }, + "3.0.0": { + "cordova-android": ">=7.0.0", + "cordova-electron": ">=3.0.0" + }, + "4.0.0": { + "cordova": ">100" + } + } + } + }, "node_modules/cordova-plugin-outline": { "resolved": "client/src/cordova/plugin", "link": true @@ -39509,6 +39304,7 @@ "@types/auto-launch": "^5.0.0", "@types/cordova": "^0.0.34", "@types/jasmine": "^4.3.6", + "@types/mocha": "^10.0.10", "@types/node": "^14.14.7", "@types/polymer": "^1.2.9", "@types/uuidv4": "^2.0.0", @@ -39523,6 +39319,7 @@ "cordova-ios": "github:apache/cordova-ios#1a5cd45e2243b239b5045a0ade9d2da1d779b72a", "cordova-lib": "^11.0.0", "cordova-plugin-clipboard": "github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0", + "cordova-plugin-device": "^3.0.0", "cordova-plugin-outline": "file:src/cordova/plugin", "cordova-plugin-splashscreen": "^6.0.0", "cordova-plugin-statusbar": "^2.2.3", @@ -40667,134 +40464,6 @@ "picomatch": "^2.2.2" } }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz", - "integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz", - "integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz", - "integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz", - "integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz", - "integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz", - "integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz", - "integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz", - "integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz", - "integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz", - "integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-s390x-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz", - "integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz", - "integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz", - "integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz", - "integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz", - "integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==", - "dev": true, - "optional": true, - "peer": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz", - "integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==", - "dev": true, - "optional": true, - "peer": true - }, "@sentry-internal/feedback": { "version": "7.92.0", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.92.0.tgz", @@ -41537,17 +41206,6 @@ "dev": true, "peer": true }, - "@types/node": { - "version": "20.14.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", - "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "undici-types": "~5.26.4" - } - }, "esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -42308,6 +41966,12 @@ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, + "@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -42367,9 +42031,9 @@ } }, "@types/polymer": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@types/polymer/-/polymer-1.2.12.tgz", - "integrity": "sha512-y22qB22+O/nQRLe7v113YBfOEGImkqT5czofd5JbwRmIHvfjfKtn+UqXuEmhydaA4eladognQ0ElDmOUYiECFA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/polymer/-/polymer-1.2.16.tgz", + "integrity": "sha512-6jK3hhs0xvoM7NlaZML89lX9+Zr24qtwuU9qApNfYbyrYBwnxtYO5BMDZ5zlGMhTFxTo3SJGUOEIa+lu/1zHhg==", "dev": true, "requires": { "@types/webcomponents.js": "*" @@ -46410,6 +46074,12 @@ "dev": true, "from": "cordova-plugin-clipboard@github:Jigsaw-Code/outline-cordova-plugin-clipboard#v2.0.0" }, + "cordova-plugin-device": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-3.0.0.tgz", + "integrity": "sha512-g8fFYOvleeYpklWvHwZ/T8/IzJe/3O0MGVDIUoqBru4v8SNDAbNVD3oOqoOQANBWGFQMg7GIkAAl8errCHZ7zQ==", + "dev": true + }, "cordova-plugin-outline": { "version": "file:client/src/cordova/plugin" }, diff --git a/package.json b/package.json index 76c93823f9..5c3472a1ba 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@storybook/web-components": "^8.2.6", "@storybook/web-components-vite": "^8.2.6", "@types/node-fetch": "^2.6.7", + "@types/polymer": "^1.2.16", "@typescript-eslint/eslint-plugin": "^8.24.1", "@typescript-eslint/parser": "^8.24.1", "@typescript-eslint/typescript-estree": "^8.24.1",