diff --git a/package-lock.json b/package-lock.json index 77f9de6..f61203a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,60 +1,76 @@ { "name": "@adnbn/plugin-remote-config", - "version": "0.1.2", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@adnbn/plugin-remote-config", - "version": "0.1.2", + "version": "0.2.0", "license": "MIT", "dependencies": { + "await-lock": "^3.0.0", "date-fns": "^2.25.0", - "validator": "^13.15.15" + "is-url": "^1.2.4" }, "devDependencies": { + "@types/is-url": "^1.2.32", "@types/react": "^19.0", "@types/react-dom": "^19.0", - "@types/validator": "^13.15.2", - "adnbn": "^0.2.7", + "adnbn": "^0.4.2", "prettier": "^3.5.3" }, "peerDependencies": { - "adnbn": ">=0.2.3", + "adnbn": ">=0.4.0", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, - "node_modules/@adnbn/browser": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@adnbn/browser/-/browser-0.1.7.tgz", - "integrity": "sha512-a+yFv4/kGUuPAR0CUCtDqej+L8RZPNGwTf3Mc++tf08ncYpHOlOOr5A2pIvYWTCy5jUaHaSi8GhCcFPm4/hwkg==", + "node_modules/@addon-core/browser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@addon-core/browser/-/browser-0.2.3.tgz", + "integrity": "sha512-q0wIKy682I8WxssCB5YmKzZzx1kD1wXur5ddCFD9zupZGmY7k+F7+w3gPh983oCGLDpTEk72s19cUfdQppIXGg==", "dev": true, "license": "MIT", - "dependencies": { - "@types/chrome": "^0.0.326" + "peerDependencies": { + "@types/chrome": "*" } }, - "node_modules/@adnbn/browser/node_modules/@types/chrome": { - "version": "0.0.326", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.326.tgz", - "integrity": "sha512-WS7jKf3ZRZFHOX7dATCZwqNJgdfiSF0qBRFxaO0LhIOvTNBrfkab26bsZwp6EBpYtqp8loMHJTnD6vDTLWPKYw==", + "node_modules/@addon-core/inject-script": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@addon-core/inject-script/-/inject-script-0.3.0.tgz", + "integrity": "sha512-BX+s3Z9yLtchCog3rW89qiq65+eQL7e2QclMr8CIEl9Lcx12gaafCg11+ur9vxqD4ARvUw3s9Mju2lCExNxFWQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" + "@addon-core/browser": "^0.2.1", + "nanoid": "^5.1.5" } }, - "node_modules/@adnbn/inject-script": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@adnbn/inject-script/-/inject-script-0.2.2.tgz", - "integrity": "sha512-qkUP8zT895YuTKdfWGM2R11iryvaur+ThxNu4rsuFchCbeFNIfe9uKU2HZRdGucJazsgM5eHxoACaPHIcFWZDw==", + "node_modules/@addon-core/storage": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@addon-core/storage/-/storage-0.4.0.tgz", + "integrity": "sha512-BTsqMr18n2xvLNa7qtzzyhkzbAqV+bpy2Qh1nRoszzl+IxPPdTZvnasWKYTkllisAfj/ktFHcXnUWT6ukORqDQ==", "dev": true, "license": "MIT", "dependencies": { - "@adnbn/browser": "^0.1.7", - "nanoid": "^5.1.5" + "@addon-core/browser": "^0.2.1", + "dequal": "^2.0.3" + }, + "peerDependencies": { + "@types/chrome": "*", + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "node_modules/@ampproject/remapping": { @@ -1881,6 +1897,23 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -3344,6 +3377,7 @@ "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/filewriter": "*" } @@ -3353,7 +3387,8 @@ "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/fs-extra": { "version": "11.0.4", @@ -3371,7 +3406,8 @@ "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/http-errors": { "version": "2.0.5", @@ -3390,6 +3426,13 @@ "@types/node": "*" } }, + "node_modules/@types/is-url": { + "version": "1.2.32", + "resolved": "https://registry.npmjs.org/@types/is-url/-/is-url-1.2.32.tgz", + "integrity": "sha512-46VLdbWI8Sc+hPexQ6NLNR2YpoDyDZIpASHkJQ2Yr+Kf9Giw6LdCTkwOdsnHKPQeh7xTjTmSnxbE8qpxYuCiHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3528,13 +3571,6 @@ "tapable": "^2.2.0" } }, - "node_modules/@types/validator": { - "version": "13.15.2", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.2.tgz", - "integrity": "sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -3788,14 +3824,16 @@ } }, "node_modules/adnbn": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/adnbn/-/adnbn-0.2.7.tgz", - "integrity": "sha512-fkkz44AGzs09LsfRwxUJr2g/AIsowPS2ElX3D7iy6/zjhHylh01La+2OAReiH8kVzZMTz/HnlUH+DqKviVPOuA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/adnbn/-/adnbn-0.4.2.tgz", + "integrity": "sha512-17o0SYuMqDIqI3xYzctxgaM8OilyrYhEeM+Yt4JHEyd044GMk0gioeM1H9/jORoz4G0rlPUlY/GSoJ0hH39nEA==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "@adnbn/browser": "^0.1.7", - "@adnbn/inject-script": "^0.2.2", + "@addon-core/browser": "^0.2.1", + "@addon-core/inject-script": "^0.3.0", + "@addon-core/storage": "^0.4.0", "@rsdoctor/rspack-plugin": "~1.1.11", "@rspack/cli": "^1.4.11", "@rspack/core": "^1.4.11", @@ -3817,18 +3855,23 @@ "rspack-plugin-virtual-module": "^0.1.13", "sass": "^1.83.4", "sass-loader": "^16.0.4", - "semver": "^7.7.2", + "semver": "^7.7.3", "source-modifier-loader": "^1.0.2", "tsconfig-paths": "^4.2.0", "type-fest": "^4.37.0", "typescript": "^5.7.3", - "validator": "^13.15.15", "webpack-merge": "^6.0.1", "zod": "^3.24.2" }, "bin": { "adnbn": "bin/adnbn.js" }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "@esbuild/darwin-arm64": "0.20.2" + }, "peerDependencies": { "@types/chrome": "*", "@types/react": "*", @@ -3988,6 +4031,12 @@ "dev": true, "license": "MIT" }, + "node_modules/await-lock": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-3.0.0.tgz", + "integrity": "sha512-eO6fLiSnrJrMdjWMNK8zbVRXPs2TKJg78iKZd9wDpN3na5tcoV6EoeiOlMgk2QaAQ1gIrK1YuMsJHXWqz89tSA==", + "license": "MIT" + }, "node_modules/axios": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", @@ -5055,6 +5104,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -6591,6 +6650,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -7113,9 +7178,9 @@ } }, "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "dev": true, "funding": [ { @@ -8176,9 +8241,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -9276,15 +9341,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/validator": { - "version": "13.15.15", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", - "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 06d15be..308b660 100644 --- a/package.json +++ b/package.json @@ -26,26 +26,27 @@ "exports": { ".": "./plugin/index.ts", "./api": "./plugin/api.ts", - "./react": "./plugin/react/index.ts", + "./hooks": "./plugin/hooks.ts", "./service": "./plugin/service.ts" }, "scripts": { "format": "prettier --write ." }, "dependencies": { + "await-lock": "^3.0.0", "date-fns": "^2.25.0", - "validator": "^13.15.15" + "is-url": "^1.2.4" }, "devDependencies": { + "@types/is-url": "^1.2.32", "@types/react": "^19.0", "@types/react-dom": "^19.0", - "@types/validator": "^13.15.2", - "adnbn": "^0.2.7", + "adnbn": "^0.4.2", "prettier": "^3.5.3" }, "peerDependencies": { + "adnbn": ">=0.4.0", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "adnbn": ">=0.2.3" + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } } diff --git a/plugin/api.ts b/plugin/api.ts index dc82e2e..a34714f 100644 --- a/plugin/api.ts +++ b/plugin/api.ts @@ -1,12 +1,37 @@ -import {getService as getServiceProxy} from "adnbn"; +import {getEnv, getService as getServiceProxy} from "adnbn"; import {getService as getServiceOrigin} from "adnbn/service"; -import {isBackground} from "@adnbn/browser"; +import {isBackground} from "@addon-core/browser"; -import {RemoteConfig} from "./types"; +import isURL from "is-url"; + +import {RemoteConfig, RemoteConfigOptions} from "./types"; export const getRemoteConfig = async (): Promise => { const service = isBackground() ? getServiceOrigin : getServiceProxy; return await service("@adnbn/plugin-remote-config/service").get(); }; + +export const getRemoteConfigOptions = (): RemoteConfigOptions => { + let options: Partial = {}; + + try { + //@ts-expect-error: __REMOTE_CONFIG_OPTIONS__ is a virtual variable generated by the bundler `plugin/index.ts` + options = __REMOTE_CONFIG_OPTIONS__; + } catch (e) { + console.error( + "Failed to load @adnbn/plugin-remote-config parameters. Make sure the plugin is properly configured in the adnbn configuration file." + ); + } + + let {url, ttl = 1440, config = {}} = options; + + if (typeof url === "string") { + url = isURL(url) ? url : getEnv(url); + } else { + url = undefined; + } + + return {ttl, config, url}; +}; \ No newline at end of file diff --git a/plugin/hooks.ts b/plugin/hooks.ts new file mode 100644 index 0000000..580f769 --- /dev/null +++ b/plugin/hooks.ts @@ -0,0 +1,19 @@ +import {useEffect, useState} from "react"; + +import {getRemoteConfig, getRemoteConfigOptions} from "./api"; + +import {RemoteConfig} from "./types"; + +export function useRemoteConfig(): T; +export function useRemoteConfig(selector: (config: T) => S): S; +export function useRemoteConfig(selector?: (config: T) => S): T | S { + const [config, setConfig] = useState(getRemoteConfigOptions().config as T); + + useEffect(() => { + getRemoteConfig() + .then(config => setConfig(config as T)) + .catch(console.error); + }, []); + + return selector ? selector(config) : config; +} diff --git a/plugin/index.ts b/plugin/index.ts index 25e5e3d..3e5cb85 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -1,23 +1,19 @@ import {DefinePlugin} from "@rspack/core"; -import {definePlugin} from "adnbn"; +import {definePlugin, getEnv} from "adnbn"; -import {RemoteConfig} from "./types"; +import isURL from "is-url"; + +import {RemoteConfig, RemoteConfigOptions, ValueOrGetter} from "./types"; export {RemoteConfig}; -export interface RemoteConfigOptions { - url?: string | (() => string); - ttl?: number | (() => number); - config?: RemoteConfig | (() => RemoteConfig); -} +export default definePlugin((options: Partial> = {}) => { + const {url = "REMOTE_CONFIG_URL", config, ttl} = options; -export default definePlugin((options: RemoteConfigOptions = {}) => { return { name: "@adnbn/plugin-remote-config", service: true, bundler: () => { - const {url = "REMOTE_CONFIG_URL", config, ttl} = options; - return { plugins: [ new DefinePlugin({ @@ -30,5 +26,14 @@ export default definePlugin((options: RemoteConfigOptions = {}) => { ], }; }, + manifest: ({manifest}) => { + const urlValue = (typeof url === "function" ? url() : url) || ""; + + const currentUrl = isURL(urlValue) ? urlValue : getEnv(urlValue); + + if (currentUrl) { + manifest.addHostPermission(currentUrl); + } + }, }; }); diff --git a/plugin/options.ts b/plugin/options.ts deleted file mode 100644 index 2525ad2..0000000 --- a/plugin/options.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {getEnv} from "adnbn"; -import isURL from "validator/lib/isURL"; - -import {RemoteConfig} from "./types"; - -export interface Options { - url?: string; - ttl: number; - config: RemoteConfig; -} - -export default (): Options => { - let options: Partial = {}; - - try { - //@ts-expect-error: __REMOTE_CONFIG_OPTIONS__ is a virtual variable generated by the bundler `plugin/index.ts` - options = __REMOTE_CONFIG_OPTIONS__; - } catch (e) { - console.error( - "Failed to load @adnbn/plugin-remote-config parameters. Make sure the plugin is properly configured in the adnbn configuration file." - ); - } - - let {url, ttl = 1440, config = {}} = options; - - if (typeof url === "string") { - url = isURL(url) ? url : getEnv(url); - } else { - url = undefined; - } - - return {ttl, config, url}; -}; diff --git a/plugin/react/RemoteConfigProvider.tsx b/plugin/react/RemoteConfigProvider.tsx deleted file mode 100644 index 7e8f3fd..0000000 --- a/plugin/react/RemoteConfigProvider.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, {PropsWithChildren, useEffect, useState} from "react"; - -import getOptions from "../options"; - -import {RemoteConfigContext} from "./context"; - -import {getRemoteConfig} from "../api"; - -import {RemoteConfig} from "../types"; - -export interface RemoteConfigProviderProps { - config?: Partial; -} - -const RemoteConfigProvider = ({children, config: propConfig}: PropsWithChildren) => { - const [config, setConfig] = useState(getOptions().config); - - useEffect(() => { - getRemoteConfig() - .then(result => setConfig(result)) - .catch(err => console.error(err)); - }, []); - - return ( - - {children} - - ); -}; - -export default RemoteConfigProvider; diff --git a/plugin/react/context.ts b/plugin/react/context.ts deleted file mode 100644 index 2a8d369..0000000 --- a/plugin/react/context.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {createContext, useContext} from "react"; - -import {RemoteConfig} from "../types"; - -export interface RemoteConfigContract { - config: RemoteConfig; -} - -export const DefaultRemoteConfig: RemoteConfigContract = { - config: {} as RemoteConfig, -}; - -export const RemoteConfigContext = createContext(DefaultRemoteConfig); - -RemoteConfigContext.displayName = "RemoteConfigContext"; - -export function useRemoteConfig(): T; -export function useRemoteConfig(selector: (config: T) => S): S; -export function useRemoteConfig(selector?: (config: T) => S): T | S { - const config = useContext(RemoteConfigContext).config as T; - - return selector ? selector(config) : config; -} diff --git a/plugin/react/index.ts b/plugin/react/index.ts deleted file mode 100644 index fd540d1..0000000 --- a/plugin/react/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {default as RemoteConfigProvider, type RemoteConfigProviderProps} from "./RemoteConfigProvider"; -export {useRemoteConfig} from "./context"; diff --git a/plugin/service.ts b/plugin/service.ts index c6171a1..4733fc0 100644 --- a/plugin/service.ts +++ b/plugin/service.ts @@ -1,14 +1,14 @@ import {defineService} from "adnbn"; -import {SecureStorage, Storage} from "adnbn/storage"; +import {SecureStorage, Storage} from "@addon-core/storage"; import formatISO from "date-fns/formatISO"; import isFuture from "date-fns/isFuture"; import parseISO from "date-fns/parseISO"; import addMinutes from "date-fns/addMinutes"; -import {awaiter} from "./utils"; +import AwaitLock from 'await-lock'; -import getOptions from "./options"; +import {getRemoteConfigOptions} from "./api"; import {RemoteConfig} from "./types"; @@ -23,17 +23,11 @@ type SecureStorageContract = { }; class RemoteConfigService { - private readonly settingsStorage = new Storage({ - area: "local", - namespace: "remote-config", - }); + private readonly settingsStorage = Storage.Local({key: "remote-config"}); - private readonly configStorage = new SecureStorage({ - area: "local", - namespace: "remote-config", - }); + private readonly configStorage = SecureStorage.Local({key: "remote-config"}); - private processing: boolean = false; + private readonly lock = new AwaitLock(); constructor( private defaultConfig: RemoteConfig, @@ -45,13 +39,7 @@ class RemoteConfigService { * @returns {Promise} */ public async get(): Promise { - try { - await awaiter(() => this.processing); - } catch (e) { - console.error("Remote Config Service - await error:", e); - } - - this.processing = true; + await this.lock.acquireAsync(); try { return await this.load(); @@ -62,40 +50,35 @@ class RemoteConfigService { return this.defaultConfig; } finally { - this.processing = false; + this.lock.release(); } } private async load(): Promise { - const storageConfig = await this.configStorage.get("config"); - const storedUrl = await this.configStorage.get("url"); - - const updatedAt = await this.settingsStorage.get("updatedAt"); - const isOrigin = await this.settingsStorage.get("isOrigin"); + const {config, url} = await this.configStorage.getAll(); + const {isOrigin, updatedAt} = await this.settingsStorage.getAll(); - const isUrlSame = !this.url || storedUrl === this.url; + const isUrlSame = !this.url || url === this.url; if ( - storageConfig && + config && isOrigin && updatedAt && isUrlSame && isFuture(addMinutes(parseISO(updatedAt), this.ttl)) ) { - return storageConfig; + return config; } const apiConfig = await this.fetch(); if (apiConfig) { - await this.setIsOrigin(true); - await this.setUpdatedAt(); - - await this.configStorage.set("config", apiConfig); - - if (this.url) { - await this.configStorage.set("url", this.url); - } + await Promise.allSettled([ + this.setIsOrigin(true), + this.setUpdatedAt(), + this.setConfig(apiConfig), + this.setUrl(this.url), + ]); return apiConfig; } @@ -130,18 +113,22 @@ class RemoteConfigService { } private async setUpdatedAt(date?: Date): Promise { - if (!date) { - date = new Date(); - } + await this.settingsStorage.set("updatedAt", formatISO(date || new Date())); + } + + private async setConfig(config: RemoteConfig): Promise { + await this.configStorage.set("config", config); + } - await this.settingsStorage.set("updatedAt", formatISO(date)); + private async setUrl(url?: string): Promise { + url && await this.configStorage.set("url", url); } } export default defineService({ permissions: ["storage"], init: () => { - const {config, ttl, url} = getOptions(); + const {config, ttl, url} = getRemoteConfigOptions(); return new RemoteConfigService(config, ttl, url); }, diff --git a/plugin/types.ts b/plugin/types.ts index 6ae4b8d..cd10535 100644 --- a/plugin/types.ts +++ b/plugin/types.ts @@ -1 +1,11 @@ export interface RemoteConfig {} + +export interface RemoteConfigOptions { + url?: string; + ttl: number; + config: Partial; +} + +export type ValueOrGetter = { + [K in keyof T]: T[K] | (() => T[K]); +}; \ No newline at end of file diff --git a/plugin/utils.ts b/plugin/utils.ts deleted file mode 100644 index b696567..0000000 --- a/plugin/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const sleep = (time: number): Promise => { - return new Promise(resolve => setTimeout(resolve, time)); -}; - -export const awaiter = async ( - check: () => boolean, - retryTime: number = 150, - timeFallback: number = 3000 -): Promise => { - let isTimeOut = false; - - const timeoutId = setTimeout(() => { - isTimeOut = true; - }, timeFallback); - - try { - while (check()) { - if (isTimeOut) { - throw new Error(`Awaiter: Timed out — ${timeFallback} ms exceeded`); - } - - await sleep(retryTime); - } - } finally { - clearTimeout(timeoutId); - } -}; diff --git a/tsconfig.json b/tsconfig.json index af418bf..1f9abbc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,5 @@ "jsx": "react-jsx" }, "include": ["**/*"], - "exclude": ["**/*.test.ts"] + "exclude": ["**/*.test.ts", "./addon/**/*.d.ts"] }