-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pull request #695: AG-19767 Migrate Firefox add-on to event pages
Merge in ADGUARD-FILTERS/tsurlfilter from feature/AG-19767 to master Squashed commit of the following: commit 8916997 Merge: b028a50 a1234dc Author: Vladimir Zhelvis <[email protected]> Date: Wed Nov 1 13:13:27 2023 +0300 Merge branch 'master' into feature/AG-19767 commit b028a50 Author: Vladimir Zhelvis <[email protected]> Date: Wed Nov 1 12:25:31 2023 +0300 update docs commit 7a21a1e Author: Vladimir Zhelvis <[email protected]> Date: Wed Nov 1 12:06:16 2023 +0300 update tests commit f91948a Author: Vladimir Zhelvis <[email protected]> Date: Fri Oct 27 15:43:32 2023 +0300 delete deprecated todo commit 789e09a Author: Vladimir Zhelvis <[email protected]> Date: Thu Oct 26 20:35:22 2023 +0300 update storage decorator commit d972184 Author: Vladimir Zhelvis <[email protected]> Date: Wed Oct 25 12:41:51 2023 +0300 update comment commit 5c8087e Author: Vladimir Zhelvis <[email protected]> Date: Wed Oct 25 12:40:12 2023 +0300 update comments commit d801043 Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 24 20:44:13 2023 +0300 update changelog commit 587e8f2 Merge: ced0261 6a209c6 Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 24 20:32:39 2023 +0300 update persistent storages commit ced0261 Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 24 20:29:50 2023 +0300 update persistent storages commit 9b681ca Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 17 12:56:07 2023 +0300 bump version commit fb6b6f0 Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 17 12:55:50 2023 +0300 update changelog commit e22cda5 Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 17 12:55:26 2023 +0300 update rollup config commit 0809fce Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 17 12:02:33 2023 +0300 fix PersistentMap util commit 9a35ea4 Merge: a3b3965 1f1fe7e Author: Vladimir Zhelvis <[email protected]> Date: Tue Oct 17 12:01:50 2023 +0300 Merge branch 'master' into feature/AG-19767 commit a3b3965 Author: Maxim Topciu <[email protected]> Date: Fri Oct 13 13:26:00 2023 +0300 AG-19767 fix tests commit 6ba4410 Author: Vladimir Zhelvis <[email protected]> Date: Fri Oct 13 10:54:21 2023 +0300 add persistent stores
- Loading branch information
Showing
27 changed files
with
554 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"jsc": { | ||
"parser": { | ||
"syntax": "typescript", | ||
"decorators": true | ||
}, | ||
"transform": { | ||
"decoratorVersion": "2022-03" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,23 @@ | ||
/* eslint-disable no-console */ | ||
import path from 'path'; | ||
import { copy } from 'fs-extra'; | ||
import { logger } from '../lib/common/utils/logger'; | ||
|
||
const REDIRECTS_CONFIG_PATH = 'redirects.yml'; | ||
const REDIRECTS_RESOURCES_SRC_PATH = 'redirect-files'; | ||
const REDIRECTS_RESOURCES_DEST_PATH = 'redirects'; | ||
|
||
const src = path.resolve(require.resolve('@adguard/scriptlets'), '../..'); | ||
|
||
// TODO: use logger from lib after import fix | ||
export const copyWar = async (dest: string): Promise<void> => { | ||
dest = path.resolve(process.cwd(), dest); | ||
|
||
try { | ||
await copy(path.resolve(src, REDIRECTS_CONFIG_PATH), path.resolve(dest, REDIRECTS_CONFIG_PATH)); | ||
await copy(path.resolve(src, REDIRECTS_RESOURCES_SRC_PATH), path.resolve(dest, REDIRECTS_RESOURCES_DEST_PATH)); | ||
|
||
logger.info(`Web accessible resources was copied to ${dest}`); | ||
console.info(`Web accessible resources was copied to ${dest}`); | ||
} catch (e) { | ||
logger.error((e as Error).message); | ||
console.error((e as Error).message); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
packages/tswebextension/src/lib/common/storage/extension-storage-decorator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* eslint-disable @typescript-eslint/explicit-function-return-type */ | ||
import type { ExtensionStorage } from './extension-storage'; | ||
|
||
/** | ||
* Creates accessor decorator for the specified storage. | ||
* | ||
* @param storage The extension storage API to use. | ||
* @returns Accessor decorator for the specified storage. | ||
* @see https://github.com/tc39/proposal-decorators | ||
*/ | ||
export function createExtensionStorageDecorator<Data extends Record<string, unknown>>( | ||
storage: ExtensionStorage<Data, string>, | ||
) { | ||
const fields = new Set<keyof Data>(); | ||
|
||
/** | ||
* Creates accessor decorator for the specified storage field. | ||
* | ||
* NOTE: You should not set the initial value to the accessor via assignment, | ||
* because decorator overwrite accessors methods and don't use private property, created on initialization. | ||
* Use Non-null assertion operator instead. | ||
* @example `@storage('foo') accessor bar!: string`; | ||
* @param field Storage field name. | ||
* @throws Error if decorator is already registered for {@link field} | ||
* or decorator is applied to class member different from auto accessor. | ||
* @returns Decorator for access to specified storage {@link field}. | ||
*/ | ||
return function createFieldDecorator<Field extends keyof Data>(field: Field) { | ||
// We prevent the use of multiple decorators on a single storage field, | ||
// because manipulating data through the accessors of multiple modules can be confusing. | ||
if (fields.has(field)) { | ||
throw new Error(`Decorator for ${String(field)} field is already registered`); | ||
} | ||
|
||
fields.add(field); | ||
|
||
return function fieldDecorator< | ||
// The type on which the class element will be defined. | ||
// For a static class element, this will be the type of the constructor. | ||
// For a non-static class element, this will be the type of the instance. | ||
This, | ||
>( | ||
_target: ClassAccessorDecoratorTarget<This, Data[Field]>, | ||
context: ClassAccessorDecoratorContext<This, Data[Field]>, | ||
): ClassAccessorDecoratorResult<This, Data[Field]> | void { | ||
if (context.kind !== 'accessor') { | ||
throw new Error('Class member is not auto accessor'); | ||
} | ||
|
||
// we do not set init descriptor, because data will be initialized asynchronously | ||
return { | ||
get(): Data[Field] { | ||
return storage.get(field); | ||
}, | ||
set(value: Data[Field]): void { | ||
return storage.set(field, value); | ||
}, | ||
}; | ||
}; | ||
}; | ||
} |
72 changes: 72 additions & 0 deletions
72
packages/tswebextension/src/lib/common/storage/extension-storage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import type { Storage } from 'webextension-polyfill'; | ||
|
||
import { PersistentValueContainer } from './persistent-value-container'; | ||
|
||
/** | ||
* API for storing persistent key-value data with debounced sync with the specified webextension storage key. | ||
* Webextension storage synchronization described in the {@link PersistentValueContainer} class. | ||
*/ | ||
export class ExtensionStorage< | ||
Data extends Record<string, unknown>, | ||
Key extends string = string, | ||
> { | ||
/** | ||
* API for storing persistent value with debounced sync with the specified webextension storage key. | ||
*/ | ||
#container: PersistentValueContainer<Key, Data>; | ||
|
||
/** | ||
* Creates {@link ExtensionStorage} instance. | ||
* @param key The key to use for storing the data. | ||
* @param api Webextension storage API. | ||
*/ | ||
constructor( | ||
key: Key, | ||
api: Storage.StorageArea, | ||
) { | ||
this.#container = new PersistentValueContainer<Key, Data>(key, api); | ||
} | ||
|
||
/** | ||
* Initializes the storage. | ||
* @param data The initial data. | ||
* @returns Promise that resolves when the storage is initialized. | ||
* @throws Error, if storage already initialized. | ||
*/ | ||
init(data: Data): Promise<void> { | ||
return this.#container.init(data); | ||
} | ||
|
||
/** | ||
* Gets the value by the specified key. | ||
* @param key The key to use for storing the value. | ||
* @throws Error, if storage not initialized. | ||
* @returns Data stored by the specified key. | ||
*/ | ||
get<T extends keyof Data>(key: T): Data[T] { | ||
return this.#container.get()[key]; | ||
} | ||
|
||
/** | ||
* Sets the value by the specified key. | ||
* @param key The key to use for storing the value. | ||
* @param value New value. | ||
* @throws Error, if storage not initialized. | ||
*/ | ||
set<T extends keyof Data>(key: T, value: Data[T]): void { | ||
const data = this.#container.get(); | ||
data[key] = value; | ||
this.#container.set(data); | ||
} | ||
|
||
/** | ||
* Deletes the value by the specified key. | ||
* @param key The key to use for storing the value. | ||
* @throws Error, if storage not initialized. | ||
*/ | ||
delete(key: keyof Data): void { | ||
const data = this.#container.get(); | ||
delete data[key]; | ||
this.#container.set(data); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { PersistentValueContainer } from './persistent-value-container'; | ||
export { ExtensionStorage } from './extension-storage'; | ||
export { createExtensionStorageDecorator } from './extension-storage-decorator'; |
141 changes: 141 additions & 0 deletions
141
packages/tswebextension/src/lib/common/storage/persistent-value-container.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { debounce } from 'lodash-es'; | ||
import browser, { type Storage, type Manifest } from 'webextension-polyfill'; | ||
|
||
/** | ||
* API to store a persistent value with debounced synchronization to the specified web extension storage key. | ||
* | ||
* After the container is created, we initialize it asynchronously to get the actual value from the storage. | ||
* The Init method is guarded against multiple initializations to avoid unnecessary reads from the memory. | ||
* Get/set methods are protected from uninitialized storage to ensure that actual data is used. | ||
* | ||
* We declare the sync get/set methods to update the cached value. This allows us to use containers in accessors. | ||
* | ||
* Set method updates the cached value and schedules the save operation to the storage via a debounce function to | ||
* avoid unnecessary writes to the storage. | ||
* | ||
* This container saves the data to storage using the specified key to avoid collisions with other instances. | ||
* It helps to avoid reading the data from the storage that is not related to the current instance. | ||
*/ | ||
export class PersistentValueContainer<Key extends string = string, Value = unknown> { | ||
// TODO: delete after the migration to event-driven background. | ||
// We do not recalculate this value because the background type cannot change at runtime. | ||
static #IS_BACKGROUND_PERSISTENT = PersistentValueContainer.#isBackgroundPersistent(); | ||
|
||
#api: Storage.StorageArea; | ||
|
||
#key: Key; | ||
|
||
#value!: Value; | ||
|
||
// TODO: make required after the migration to event-driven background. | ||
#save?: () => void; | ||
|
||
#isInitialized = false; | ||
|
||
/** | ||
* Creates {@link PersistentValueContainer} instance. | ||
* @param key The key to use for storing the data. | ||
* @param api Webextension storage API. | ||
* @param debounceMs The debounce time in milliseconds to save the data to the storage. | ||
* Optional. Default is 300ms. | ||
*/ | ||
constructor( | ||
key: Key, | ||
api: Storage.StorageArea, | ||
debounceMs = 300, | ||
) { | ||
this.#key = key; | ||
this.#api = api; | ||
|
||
/** | ||
* TODO: remove this condition after the migration to event-driven background. | ||
*/ | ||
if (!PersistentValueContainer.#IS_BACKGROUND_PERSISTENT) { | ||
this.#save = debounce(() => { | ||
this.#api.set({ [this.#key]: this.#value }); | ||
}, debounceMs); | ||
} | ||
} | ||
|
||
/** | ||
* Initializes the value. | ||
* @param value The initial value. | ||
* @returns Promise that resolves when the value is initialized. | ||
* @throws Error, if storage already initialized. | ||
*/ | ||
async init(value: Value): Promise<void> { | ||
if (this.#isInitialized) { | ||
throw new Error('Storage already initialized'); | ||
} | ||
|
||
if (PersistentValueContainer.#IS_BACKGROUND_PERSISTENT) { | ||
this.#value = value; | ||
} else { | ||
const storageData = await this.#api.get({ | ||
[this.#key]: value, | ||
}); | ||
|
||
this.#value = storageData[this.#key]; | ||
} | ||
|
||
this.#isInitialized = true; | ||
} | ||
|
||
/** | ||
* Gets the value. | ||
* @returns The value stored by the specified key. | ||
* @throws Error, if storage not initialized. | ||
*/ | ||
get(): Value { | ||
this.#checkIsInitialized(); | ||
|
||
return this.#value; | ||
} | ||
|
||
/** | ||
* Sets the value. | ||
* @param value Value to be stored in the specified key. | ||
* @throws Error, if storage not initialized. | ||
*/ | ||
set(value: Value): void { | ||
this.#checkIsInitialized(); | ||
|
||
this.#value = value; | ||
|
||
if (this.#save) { | ||
this.#save(); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the storage is initialized. | ||
* @throws Error, if storage not initialized. | ||
*/ | ||
#checkIsInitialized(): void { | ||
if (!this.#isInitialized) { | ||
throw new Error('Storage not initialized'); | ||
} | ||
} | ||
|
||
/** | ||
* TODO: remove this method after the migration to event-driven background. | ||
* Checks if the background script is persistent. | ||
* @returns True if the background script is persistent. | ||
*/ | ||
static #isBackgroundPersistent(): boolean { | ||
const manifest = browser.runtime.getManifest(); | ||
|
||
if (manifest.manifest_version === 3) { | ||
return false; | ||
} | ||
|
||
if (!manifest.background) { | ||
return true; | ||
} | ||
|
||
const background = manifest.background as | ||
(Manifest.WebExtensionManifestBackgroundC2Type | Manifest.WebExtensionManifestBackgroundC1Type); | ||
|
||
return background.persistent ?? true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.