diff --git a/src/entities/tab/index.ts b/src/entities/tab/index.ts index ef30951..21e24c8 100644 --- a/src/entities/tab/index.ts +++ b/src/entities/tab/index.ts @@ -1,9 +1,9 @@ import { atom, onMount, task } from 'nanostores' -import { ITab, browser } from '@shared/browser' +import { TTab, browser } from '@shared/browser' import { addToast, getErrorToast } from '@shared/ui/Toast' -export const $activeTab = atom(null) +export const $activeTab = atom(null) onMount($activeTab, () => { task(async () => { diff --git a/src/shared/browser/__mocks__/chrome.ts b/src/shared/browser/__mocks__/chrome.ts index 73fc220..f495286 100644 --- a/src/shared/browser/__mocks__/chrome.ts +++ b/src/shared/browser/__mocks__/chrome.ts @@ -1,9 +1,12 @@ import { MockedObject, vi } from 'vitest' +import { TOnChangeListenerProps } from '../types' + type TChrome = typeof chrome type TStorageValue = Record let store: TStorageValue = {} +let listeners: Array<(changes: TOnChangeListenerProps) => void> = [] function getStorageValueByKey(key: string) { const result: TStorageValue = {} @@ -11,6 +14,10 @@ function getStorageValueByKey(key: string) { return result } +export const chromeMockClearListeners = vi.fn(() => { + listeners = [] +}) + export const chromeMock = { storage: { local: { @@ -20,14 +27,28 @@ export const chromeMock = { }), set: vi.fn((payload, cb) => { - Object.keys(payload).forEach((key) => (store[key] = payload[key])) + const changes: TOnChangeListenerProps = {} + Object.keys(payload).forEach((key) => { + if (payload[key]) { + store[key] = payload[key] + changes[key] = { oldValue: store[key], newValue: payload[key] } + } + }) + + for (const listener of listeners) { + listener(changes) + } + return cb ? cb() : Promise.resolve() }), onChanged: { - addListener: vi.fn(), + addListener: vi.fn( + (listener: (changes: TOnChangeListenerProps) => void) => { + listeners.push(listener) + }, + ), removeListener: vi.fn(), - hasListener: vi.fn(), }, clear: vi.fn((cb) => { diff --git a/src/shared/browser/__test__/chrome.test.ts b/src/shared/browser/__test__/chrome.test.ts index 24ac3c5..c502ef5 100644 --- a/src/shared/browser/__test__/chrome.test.ts +++ b/src/shared/browser/__test__/chrome.test.ts @@ -114,7 +114,11 @@ describe('browser', () => { }) }) - chromeBrowser.storage.local.onChanged(KEY, callback, defaultValue) + chromeBrowser.storage.local.onChanged.addListener( + KEY, + callback, + defaultValue, + ) expect(callback).toHaveBeenCalledWith( Result.Success({ newValue, oldValue }), @@ -136,7 +140,11 @@ describe('browser', () => { }) }) - chromeBrowser.storage.local.onChanged(KEY, callback, defaultValue) + chromeBrowser.storage.local.onChanged.addListener( + KEY, + callback, + defaultValue, + ) expect(callback).toHaveBeenCalledTimes(2) @@ -170,7 +178,11 @@ describe('browser', () => { }) }) - chromeBrowser.storage.local.onChanged(KEY, callback, defaultValue) + chromeBrowser.storage.local.onChanged.addListener( + KEY, + callback, + defaultValue, + ) const call = callback.mock.calls[0] expect(call[0].data).toBeNull() diff --git a/src/shared/browser/chrome.ts b/src/shared/browser/chrome.ts index 3ddacb7..7085186 100644 --- a/src/shared/browser/chrome.ts +++ b/src/shared/browser/chrome.ts @@ -1,7 +1,9 @@ +import { TLanguageCode } from '@entities/language' + import { Result } from '@shared/libs/operationResult' import { PortReceiver } from './port' -import { IBrowser } from './types' +import { IBrowser, TOnChangeListenerProps } from './types' export const chromeBrowser: IBrowser = { storage: { @@ -46,39 +48,46 @@ export const chromeBrowser: IBrowser = { }) }, - onChanged: (key, callback, defaultValue) => { - chrome.storage.local.onChanged.addListener((changes) => { - if (changes[key]) { - let oldValue = defaultValue - try { - oldValue = JSON.parse(changes[key].oldValue) - } catch (error) { - callback( - Result.Error({ - type: `ERROR_CAN_NOT_GET_OLD_DATA_FROM_STORAGE`, - error: error instanceof Error ? error : null, - }), - ) - } + onChanged: { + addListener: (key, callback, defaultValue) => { + const listener = (changes: TOnChangeListenerProps) => { + if (changes[key]) { + let oldValue = defaultValue + try { + oldValue = JSON.parse(changes[key].oldValue as string) + } catch (error) { + callback( + Result.Error({ + type: `ERROR_CAN_NOT_GET_OLD_DATA_FROM_STORAGE`, + error: error instanceof Error ? error : null, + }), + ) + } - try { - const newValue = JSON.parse(changes[key].newValue) - callback( - Result.Success({ - newValue: newValue, - oldValue: oldValue, - }), - ) - } catch (error) { - callback( - Result.Error({ - type: `ERROR_CAN_NOT_GET_NEW_DATA_FROM_STORAGE`, - error: error instanceof Error ? error : null, - }), - ) + try { + const newValue = JSON.parse(changes[key].newValue as string) + callback( + Result.Success({ + newValue: newValue, + oldValue: oldValue, + }), + ) + } catch (error) { + callback( + Result.Error({ + type: `ERROR_CAN_NOT_GET_NEW_DATA_FROM_STORAGE`, + error: error instanceof Error ? error : null, + }), + ) + } } } - }) + chrome.storage.local.onChanged.addListener(listener) + return listener + }, + removeListener: (listener) => { + chrome.storage.local.onChanged.removeListener(listener) + }, }, }, }, @@ -87,6 +96,15 @@ export const chromeBrowser: IBrowser = { getMessage: (key, substitutions) => { return chrome.i18n.getMessage(key, substitutions) }, + + detectLanguage: async (text: string) => { + if (!text) return 'other' + + const detectLanguageResult = await chrome.i18n.detectLanguage(text) + + return (detectLanguageResult.languages[0].language ?? + 'other') as TLanguageCode + }, }, runtime: { diff --git a/src/shared/browser/index.ts b/src/shared/browser/index.ts index dd8dfd9..057b108 100644 --- a/src/shared/browser/index.ts +++ b/src/shared/browser/index.ts @@ -1,6 +1,7 @@ import { chromeBrowser } from './chrome' -import { IBrowser } from './types' +import type { IBrowser } from './types' export const browser: IBrowser = chromeBrowser -export type { ITab } from './types' + +export type { TTab, TOnChangeListenerProps } from './types' export * from './port' diff --git a/src/shared/browser/types.ts b/src/shared/browser/types.ts index be56c9a..e2dbee0 100644 --- a/src/shared/browser/types.ts +++ b/src/shared/browser/types.ts @@ -1,3 +1,5 @@ +import { TLanguageCode } from '@entities/language' + import type { TResult } from '@shared/libs/operationResult' import { PortReceiver } from '.' @@ -9,21 +11,32 @@ export interface IBrowser { set: (key: string, value: Data) => Promise> - onChanged: ( - key: string, - callback: ( - changes: TResult<{ - newValue: Data - oldValue: Data - }>, - ) => void, - defaultValue: Data, - ) => void + onChanged: { + addListener: ( + key: string, + callback: ( + changes: TResult<{ + newValue: Data + oldValue: Data + }>, + ) => void, + defaultValue: Data, + ) => (changes: { + [key: string]: { newValue?: Data; oldValue?: Data } + }) => void + removeListener: ( + callback: (changes: { + [key: string]: { newValue?: Data; oldValue?: Data } + }) => void, + ) => void + } } } i18n: { getMessage: (key: string, substitutions?: string | string[]) => string + + detectLanguage: (text: string) => Promise } runtime: { @@ -33,9 +46,15 @@ export interface IBrowser { } tabs: { - getActiveTab: () => Promise> + getActiveTab: () => Promise> } } -export type ITab = { id: number; url: string } -export type IActiveTabInfo = { tabId: number } +export type TTab = { id: number; url: string } +export type TActiveTabInfo = { tabId: number } +export type TOnChangeListenerProps = { + [key: string]: { + newValue?: Value + oldValue?: Value + } +}