diff --git a/@shared/constants/soroban/token.ts b/@shared/constants/soroban/token.ts index 290e0ec671..0f5572b452 100644 --- a/@shared/constants/soroban/token.ts +++ b/@shared/constants/soroban/token.ts @@ -47,8 +47,7 @@ export const DEFAULT_ASSETS_LISTS: AssetsLists = { isEnabled: true, }, { - url: - "https://raw.githubusercontent.com/soroswap/token-list/main/tokenList.json", + url: "https://raw.githubusercontent.com/soroswap/token-list/main/tokenList.json", isEnabled: true, }, { diff --git a/extension/e2e-tests/test-fixtures.ts b/extension/e2e-tests/test-fixtures.ts index aefb0f0a44..0778cce72a 100644 --- a/extension/e2e-tests/test-fixtures.ts +++ b/extension/e2e-tests/test-fixtures.ts @@ -20,13 +20,12 @@ export const test = base.extend<{ }, extensionId: async ({ context }, use) => { // for manifest v2: - let [background] = context.backgroundPages(); - if (!background) background = await context.waitForEvent("backgroundpage"); + // let [background] = context.backgroundPages(); + // if (!background) background = await context.waitForEvent("backgroundpage"); // for manifest v3: - // let [background] = context.serviceWorkers(); - // if (!background) - // background = await context.waitForEvent('serviceworker'); + let [background] = context.serviceWorkers(); + if (!background) background = await context.waitForEvent("serviceworker"); const extensionId = background.url().split("/")[2]; await use(extensionId); diff --git a/extension/package.json b/extension/package.json index 6630ccdb7e..ed0fdf6fa4 100644 --- a/extension/package.json +++ b/extension/package.json @@ -111,4 +111,4 @@ "stellar-sdk>stellar-base>sodium-native": false } } -} \ No newline at end of file +} diff --git a/extension/public/background.ts b/extension/public/background.ts index 388e02d857..8241c1efa4 100644 --- a/extension/public/background.ts +++ b/extension/public/background.ts @@ -5,14 +5,11 @@ import { initAlarmListener, } from "background"; -import { buildStore } from "background/store"; - -async function main() { - const store = await buildStore(); +function main() { initContentScriptMessageListener(); - initExtensionMessageListener(store); + initExtensionMessageListener(); initInstalledListener(); - initAlarmListener(store); + initAlarmListener(); } main(); diff --git a/extension/public/static/manifest/v2.json b/extension/public/static/manifest/v2.json index 9bdb11b129..f889742c04 100644 --- a/extension/public/static/manifest/v2.json +++ b/extension/public/static/manifest/v2.json @@ -10,19 +10,13 @@ } }, "background": { - "scripts": [ - "background.min.js" - ], + "scripts": ["background.min.js"], "persistent": true }, "content_scripts": [ { - "matches": [ - "" - ], - "js": [ - "contentScript.min.js" - ], + "matches": [""], + "js": ["contentScript.min.js"], "run_at": "document_start" } ], @@ -41,9 +35,6 @@ "48": "images/icon48.png", "128": "images/icon128.png" }, - "permissions": [ - "storage", - "alarms" - ], + "permissions": ["storage", "alarms"], "manifest_version": 2 -} \ No newline at end of file +} diff --git a/extension/public/static/manifest/v3.json b/extension/public/static/manifest/v3.json index 563d3ca57a..25f2b5eada 100644 --- a/extension/public/static/manifest/v3.json +++ b/extension/public/static/manifest/v3.json @@ -3,17 +3,20 @@ "version": "5.18.5", "version_name": "5.18.5", "description": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser.", + "browser_specific_settings": { + "gecko": { + "id": "{3ee0dd4e-8c64-4b92-b539-25718a10f62f}", + "strict_min_version": "48.0" + } + }, "background": { - "service_worker": "background.min.js" + "service_worker": "background.min.js", + "scripts": ["background.min.js"] }, "content_scripts": [ { - "matches": [ - "" - ], - "js": [ - "contentScript.min.js" - ], + "matches": [""], + "js": ["contentScript.min.js"], "run_at": "document_start" } ], @@ -32,9 +35,6 @@ "48": "images/icon48.png", "128": "images/icon128.png" }, - "permissions": [ - "storage", - "alarms" - ], + "permissions": ["storage", "alarms"], "manifest_version": 3 -} \ No newline at end of file +} diff --git a/extension/src/background/helpers/dataStorage.ts b/extension/src/background/helpers/dataStorage.ts index 50717a70c8..856efb3ccb 100644 --- a/extension/src/background/helpers/dataStorage.ts +++ b/extension/src/background/helpers/dataStorage.ts @@ -39,7 +39,7 @@ export const browserLocalStorage = storage?.local; export const browserSessionStorage = storage?.session; // Session Storage Feature Flag - turn on when storage.session is supported -export const SESSION_STORAGE_ENABLED = false; +export const SESSION_STORAGE_ENABLED = true; export type StorageOption = | typeof browserLocalStorage @@ -62,6 +62,9 @@ export const dataStorage = ( clear: async () => { await storageApi.clear(); }, + remove: async (keys: string | string[]) => { + await storageApi.remove(keys); + }, }); export const dataStorageAccess = ( @@ -74,12 +77,13 @@ export const dataStorageAccess = ( await store.setItem({ [keyId]: value }); }, clear: () => store.clear(), + remove: store.remove, }; }; export const normalizeMigratedData = async () => { const localStore = dataStorageAccess(browserLocalStorage); - const localStorageEntries = Object.entries(localStorage); + const localStorageEntries = await browserLocalStorage.get(null); const applicationState = await localStore.getItem(APPLICATION_ID); const isLocalStoreSetup = !!applicationState?.length; @@ -95,7 +99,7 @@ export const normalizeMigratedData = async () => { if (typeof value === "string") { const parsedValue = JSON.parse(value); // eslint-disable-next-line no-await-in-loop - await localStore.setItem(key, parsedValue); + await localStore.setItem(key as string, parsedValue); } } catch (e) { // do not transform v @@ -148,17 +152,17 @@ export const migrateSorobanRpcUrlNetworkDetails = async () => { // This migration migrates the storage for custom tokens IDs to be keyed by network const migrateTokenIdList = async () => { const localStore = dataStorageAccess(browserLocalStorage); - const tokenIdsByKey = (await localStore.getItem(TOKEN_ID_LIST)) as Record< - string, - object - >; + const tokenIdsByKey = await localStore.getItem(TOKEN_ID_LIST); const storageVersion = (await localStore.getItem(STORAGE_VERSION)) as string; if (!storageVersion || semver.lt(storageVersion, "1.0.0")) { - const newTokenList = { - [NETWORKS.FUTURENET]: tokenIdsByKey, - }; - await localStore.setItem(TOKEN_ID_LIST, newTokenList); + if (Array.isArray(tokenIdsByKey)) { + const newTokenList = { + [NETWORKS.FUTURENET]: tokenIdsByKey, + }; + await localStore.setItem(TOKEN_ID_LIST, newTokenList); + } + await migrateDataStorageVersion("1.0.0"); } }; @@ -247,6 +251,7 @@ const migrateSorobanRpcUrlNetwork = async () => { NETWORK_ID, ); if ( + migratedNetwork && migratedNetwork.network === NETWORKS.FUTURENET && !migratedNetwork.sorobanRpcUrl ) { diff --git a/extension/src/background/index.ts b/extension/src/background/index.ts index 2504628622..518dfa6121 100644 --- a/extension/src/background/index.ts +++ b/extension/src/background/index.ts @@ -1,10 +1,10 @@ import browser from "webextension-polyfill"; -import { Store } from "redux"; import { ROUTES } from "popup/constants/routes"; import { EXTERNAL_SERVICE_TYPES, SERVICE_TYPES, } from "@shared/constants/services"; +import { buildStore } from "background/store"; import { popupMessageListener } from "./messageListener/popupMessageListener"; import { freighterApiMessageListener } from "./messageListener/freighterApiMessageListener"; @@ -27,8 +27,9 @@ export const initContentScriptMessageListener = () => { }); }; -export const initExtensionMessageListener = (sessionStore: Store) => { +export const initExtensionMessageListener = () => { browser?.runtime?.onMessage?.addListener(async (request, sender) => { + const sessionStore = await buildStore(); // todo this is kinda ugly let res; if (Object.values(SERVICE_TYPES).includes(request.type as SERVICE_TYPES)) { @@ -70,8 +71,9 @@ export const initInstalledListener = () => { browser?.runtime?.onInstalled.addListener(versionedMigration); }; -export const initAlarmListener = (sessionStore: Store) => { - browser?.alarms?.onAlarm.addListener(({ name }: { name: string }) => { +export const initAlarmListener = () => { + browser?.alarms?.onAlarm.addListener(async ({ name }: { name: string }) => { + const sessionStore = await buildStore(); if (name === SESSION_ALARM_NAME) { sessionStore.dispatch(timeoutAccountAccess()); } diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index ca5c53174e..dffbf75680 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -34,6 +34,7 @@ import { MessageResponder } from "background/types"; import { ALLOWLIST_ID, + ACCOUNT_NAME_LIST_ID, APPLICATION_ID, ASSETS_LISTS_ID, CACHED_ASSET_ICONS_ID, @@ -766,7 +767,21 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { publicKey: wallet.getPublicKey(0), privateKey: wallet.getSecret(0), }; - localStore.clear(); + + const keyIdList = await getKeyIdList(); + + if (keyIdList.length) { + /* Clear any existing account data while maintaining app settings */ + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < keyIdList.length; i += 1) { + await localStore.remove(`stellarkeys:${keyIdList[i]}`); + } + + await localStore.setItem(KEY_ID_LIST, []); + await localStore.remove(ACCOUNT_NAME_LIST_ID); + } + await localStore.setItem(KEY_DERIVATION_NUMBER_ID, "0"); await _storeAccount({ diff --git a/extension/src/helpers/metrics.ts b/extension/src/helpers/metrics.ts index 7ecad53030..9a93e32bc8 100644 --- a/extension/src/helpers/metrics.ts +++ b/extension/src/helpers/metrics.ts @@ -19,13 +19,15 @@ const handlersLookup: { [key: string]: MetricHandler[] } = {}; * intended for metrics emission, nothing else. */ export function metricsMiddleware(): Middleware { - return ({ getState }) => (next) => (action: AnyAction) => { - const state = getState(); - (handlersLookup[action.type] || []).forEach((handler) => - handler(state, action), - ); - return next(action); - }; + return ({ getState }) => + (next) => + (action: AnyAction) => { + const state = getState(); + (handlersLookup[action.type] || []).forEach((handler) => + handler(state, action), + ); + return next(action); + }; } // I can't figure out how to get the properties off a thunk for the ActionType diff --git a/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx b/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx index 3b1fd63b06..7fadcd2c4f 100644 --- a/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx +++ b/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx @@ -45,9 +45,8 @@ export const HardwareSign = ({ transactionData: { destination }, } = useSelector(transactionSubmissionSelector); const bipPath = useSelector(bipPathSelector); - const [hardwareConnectSuccessful, setHardwareConnectSuccessful] = useState( - false, - ); + const [hardwareConnectSuccessful, setHardwareConnectSuccessful] = + useState(false); const [connectError, setConnectError] = useState(""); const isSwap = useIsSwap(); const [isDetectBtnDirty, setIsDetectBtnDirty] = useState(false); diff --git a/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx b/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx index dcdb5687cf..418f71844b 100644 --- a/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx +++ b/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx @@ -59,13 +59,8 @@ export const ModifyAssetList = ({ ({ url }) => url === decodedAssetListUrl, ); if (assetsListsSelection) { - const { - url, - name, - description, - provider, - isEnabled, - } = assetsListsSelection; + const { url, name, description, provider, isEnabled } = + assetsListsSelection; setAssetListInfo({ url, name, diff --git a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx index b52abc5dd0..e3d55a3811 100644 --- a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx +++ b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx @@ -80,9 +80,8 @@ export const NetworkForm = ({ isEditing }: NetworkFormProps) => { ? { ...networkDetailsToEdit, isSwitchSelected: false, - isAllowHttpSelected: !networkDetailsToEdit?.networkUrl.includes( - "https", - ), + isAllowHttpSelected: + !networkDetailsToEdit?.networkUrl.includes("https"), } : { networkName: "", @@ -170,9 +169,8 @@ export const NetworkForm = ({ isEditing }: NetworkFormProps) => { }), ); - const addCustomNetworkFulfilled = addCustomNetwork.fulfilled.match( - addCustomNetworkRes, - ); + const addCustomNetworkFulfilled = + addCustomNetwork.fulfilled.match(addCustomNetworkRes); let changeNetworkFulfilled = true; if (values.isSwitchSelected) { diff --git a/extension/src/popup/components/signTransaction/Operations/index.tsx b/extension/src/popup/components/signTransaction/Operations/index.tsx index 4c21c84875..7fe4dbcb56 100644 --- a/extension/src/popup/components/signTransaction/Operations/index.tsx +++ b/extension/src/popup/components/signTransaction/Operations/index.tsx @@ -172,14 +172,8 @@ export const Operations = ({ } case "pathPaymentStrictReceive": { - const { - sendAsset, - sendMax, - destination, - destAsset, - destAmount, - path, - } = op; + const { sendAsset, sendMax, destination, destAsset, destAmount, path } = + op; return ( <> @@ -661,7 +644,7 @@ export const Operations = ({ ); } if (type === "revokeAccountSponsorship") { - const _op = (op as unknown) as Operation.RevokeAccountSponsorship; + const _op = op as unknown as Operation.RevokeAccountSponsorship; const { account } = _op; return ( @@ -687,7 +670,7 @@ export const Operations = ({ ); } if (type === "revokeDataSponsorship") { - const _op = (op as unknown) as Operation.RevokeDataSponsorship; + const _op = op as unknown as Operation.RevokeDataSponsorship; const { account, name } = _op; return ( <> @@ -700,7 +683,8 @@ export const Operations = ({ ); } if (type === "revokeClaimableBalanceSponsorship") { - const _op = (op as unknown) as Operation.RevokeClaimableBalanceSponsorship; + const _op = + op as unknown as Operation.RevokeClaimableBalanceSponsorship; const { balanceId } = _op; return ( diff --git a/extension/src/popup/ducks/accountServices.ts b/extension/src/popup/ducks/accountServices.ts index f3fe8c5583..659e3f4dce 100644 --- a/extension/src/popup/ducks/accountServices.ts +++ b/extension/src/popup/ducks/accountServices.ts @@ -603,9 +603,8 @@ const authSlice = createSlice({ }; }); builder.addCase(makeAccountActive.rejected, (state, action) => { - const { - message = "Freighter was unable to switch to this account", - } = action.error; + const { message = "Freighter was unable to switch to this account" } = + action.error; return { ...state, @@ -624,9 +623,8 @@ const authSlice = createSlice({ }; }); builder.addCase(updateAccountName.rejected, (state, action) => { - const { - message = "Freighter was unable update this account's name", - } = action.error; + const { message = "Freighter was unable update this account's name" } = + action.error; return { ...state, @@ -783,17 +781,13 @@ const authSlice = createSlice({ }; }); builder.addCase(migrateAccounts.fulfilled, (state, action) => { - const { - publicKey, - allAccounts, - migratedAccounts, - hasPrivateKey, - } = action.payload || { - publicKey: "", - allAccounts: [], - migratedAccounts: [], - hasPrivateKey: false, - }; + const { publicKey, allAccounts, migratedAccounts, hasPrivateKey } = + action.payload || { + publicKey: "", + allAccounts: [], + migratedAccounts: [], + hasPrivateKey: false, + }; return { ...state, diff --git a/extension/src/popup/helpers/__tests__/searchAsset.test.js b/extension/src/popup/helpers/__tests__/searchAsset.test.js index 03deac7636..f98e59c375 100644 --- a/extension/src/popup/helpers/__tests__/searchAsset.test.js +++ b/extension/src/popup/helpers/__tests__/searchAsset.test.js @@ -15,8 +15,7 @@ const validAssetList = { name: "yXLM", org: "Ultra Capital LLC dba Ultra Capital", domain: "ultracapital.xyz", - icon: - "https://ipfs.io/ipfs/bafkreihntcz2lpaxawmbhwidtuifladkgew6olwuly2dz5pewqillhhpay", + icon: "https://ipfs.io/ipfs/bafkreihntcz2lpaxawmbhwidtuifladkgew6olwuly2dz5pewqillhhpay", decimals: 7, }, ], @@ -97,8 +96,7 @@ describe("searchAsset", () => { name: "yXLM", org: "Ultra Capital LLC dba Ultra Capital", domain: "ultracapital.xyz", - icon: - "https://ipfs.io/ipfs/bafkreihntcz2lpaxawmbhwidtuifladkgew6olwuly2dz5pewqillhhpay", + icon: "https://ipfs.io/ipfs/bafkreihntcz2lpaxawmbhwidtuifladkgew6olwuly2dz5pewqillhhpay", decimals: 7, }, ], diff --git a/extension/src/popup/views/Account/index.tsx b/extension/src/popup/views/Account/index.tsx index cc020c0581..27222c376b 100644 --- a/extension/src/popup/views/Account/index.tsx +++ b/extension/src/popup/views/Account/index.tsx @@ -69,9 +69,8 @@ export const Account = () => { transactionSubmissionSelector, ); const accountStatus = useSelector(accountStatusSelector); - const [isAccountFriendbotFunded, setIsAccountFriendbotFunded] = useState( - false, - ); + const [isAccountFriendbotFunded, setIsAccountFriendbotFunded] = + useState(false); const publicKey = useSelector(publicKeySelector); const networkDetails = useSelector(settingsNetworkDetailsSelector); diff --git a/extension/src/popup/views/AddAccount/AddAccount/index.tsx b/extension/src/popup/views/AddAccount/AddAccount/index.tsx index a25dd38fb1..8d670bbef2 100644 --- a/extension/src/popup/views/AddAccount/AddAccount/index.tsx +++ b/extension/src/popup/views/AddAccount/AddAccount/index.tsx @@ -50,9 +50,10 @@ export const AddAccount = () => { } }; - useEffect(() => () => (dispatch(clearApiError()) as unknown) as void, [ - dispatch, - ]); + useEffect( + () => () => dispatch(clearApiError()) as unknown as void, + [dispatch], + ); return ( diff --git a/extension/src/popup/views/IntegrationTest.tsx b/extension/src/popup/views/IntegrationTest.tsx index fa6a42d699..5878c22d74 100644 --- a/extension/src/popup/views/IntegrationTest.tsx +++ b/extension/src/popup/views/IntegrationTest.tsx @@ -69,7 +69,7 @@ const testCustomNetwork = { networkUrl: NETWORK_URLS.TESTNET, networkPassphrase: Networks.TESTNET, }; -const testBalances = ({ +const testBalances = { native: { token: { type: "native", code: "XLM" }, }, @@ -84,7 +84,7 @@ const testBalances = ({ }, }, }, -} as unknown) as Balances; +} as unknown as Balances; export const IntegrationTest = () => { const [isDone, setIsDone] = useState(false); diff --git a/extension/src/popup/views/ManageAssets/index.tsx b/extension/src/popup/views/ManageAssets/index.tsx index dfb47db6ae..c8a0fa707f 100644 --- a/extension/src/popup/views/ManageAssets/index.tsx +++ b/extension/src/popup/views/ManageAssets/index.tsx @@ -17,12 +17,8 @@ import { PrivateKeyRoute } from "popup/Router"; import { ROUTES } from "popup/constants/routes"; export const ManageAssets = () => { - const { - accountBalances, - destinationBalances, - assetSelect, - error, - } = useSelector(transactionSubmissionSelector); + const { accountBalances, destinationBalances, assetSelect, error } = + useSelector(transactionSubmissionSelector); const { networkPassphrase } = useSelector(settingsNetworkDetailsSelector); const [errorAsset, setErrorAsset] = useState(""); diff --git a/extension/src/popup/views/__tests__/ManageAssets.test.tsx b/extension/src/popup/views/__tests__/ManageAssets.test.tsx index b40d4be8b4..cce62ba2b9 100644 --- a/extension/src/popup/views/__tests__/ManageAssets.test.tsx +++ b/extension/src/popup/views/__tests__/ManageAssets.test.tsx @@ -41,7 +41,7 @@ const unverifiedToken = "CAZXRTOKNUQ2JQQF3NCRU7GYMDJNZ2NMQN6IGN4FCT5DWPODMPVEXSND"; const manageAssetsMockBalances = { - balances: ({ + balances: { "USDC:GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM": { token: { code: "USDC", @@ -67,7 +67,7 @@ const manageAssetsMockBalances = { total: new BigNumber("0"), available: new BigNumber("0"), }, - } as any) as Balances, + } as any as Balances, isFunded: true, subentryCount: 1, tokensWithNoBalance: [], diff --git a/extension/src/popup/views/__tests__/Swap.test.tsx b/extension/src/popup/views/__tests__/Swap.test.tsx index d69e4fb0a5..7f61e49285 100644 --- a/extension/src/popup/views/__tests__/Swap.test.tsx +++ b/extension/src/popup/views/__tests__/Swap.test.tsx @@ -25,7 +25,7 @@ import { Swap } from "popup/views/Swap"; import { Wrapper, mockAccounts } from "../../__testHelpers__"; export const swapMockBalances = { - balances: ({ + balances: { "USDC:GCK3D3V2XNLLKRFGFFFDEJXA4O2J4X36HET2FE446AV3M4U7DPHO3PEM": { token: { code: "USDC", @@ -51,7 +51,7 @@ export const swapMockBalances = { total: new BigNumber("333"), available: new BigNumber("333"), }, - } as any) as Balances, + } as any as Balances, isFunded: true, subentryCount: 1, tokensWithNoBalance: [], diff --git a/extension/src/popup/views/__tests__/SwapUnfunded.test.tsx b/extension/src/popup/views/__tests__/SwapUnfunded.test.tsx index 8e9f8df9b4..819ffc53f4 100644 --- a/extension/src/popup/views/__tests__/SwapUnfunded.test.tsx +++ b/extension/src/popup/views/__tests__/SwapUnfunded.test.tsx @@ -19,7 +19,7 @@ import { Wrapper, mockAccounts } from "../../__testHelpers__"; const publicKey = "GCXRLIZUQNZ3YYJDGX6Z445P7FG5WXT7UILBO5CFIYYM7Z7YTIOELC6O"; export const swapMockBalances = { - balances: ({} as any) as Balances, + balances: {} as any as Balances, isFunded: true, subentryCount: 1, tokensWithNoBalance: [], diff --git a/extension/webpack.common.js b/extension/webpack.common.js index 234c8021a7..1c5ce243ef 100644 --- a/extension/webpack.common.js +++ b/extension/webpack.common.js @@ -16,10 +16,7 @@ const commonConfig = ( entry: { background: path.resolve(__dirname, "./public/background.ts"), index: ["babel-polyfill", path.resolve(__dirname, "./src/popup/index.tsx")], - contentScript: [ - "babel-polyfill", - path.resolve(__dirname, "./public/contentScript.ts"), - ], + contentScript: [path.resolve(__dirname, "./public/contentScript.ts")], }, watchOptions: { ignored: ["node_modules/**/*", "build/**/*"], @@ -125,7 +122,7 @@ const commonConfig = ( to: BUILD_PATH, }, { - from: path.resolve(__dirname, "./public/static/manifest/v2.json"), + from: path.resolve(__dirname, "./public/static/manifest/v3.json"), to: `${BUILD_PATH}/manifest.json`, }, ]),