diff --git a/README.md b/README.md index 6b24f91..be5d73e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Module to enhance ability of source replacement for website +Note: Not using this in production environment + ## Installation ``` @@ -15,16 +17,14 @@ yarn add source-replacement ### Usage -#### In your source +Attach `source-replacement/build/executors/source-replacement` on the script tag in `` or mark it as `async type=module` -``` -import 'source-replacement' -``` +In your source import `source-replacement/build/executors/code-blocker` to prevent executing your source during the process of replacement #### On your browser Enter page with the following example ``` -https://example.com/#replacementTarget=https://your-target-js-source-url +https://example.com/#targetReplacementSource=https://your-target-js-source-url ``` diff --git a/package.json b/package.json index f8a8932..6166764 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "name": "source-replacement", - "version": "0.0.0", - "main": "build/index.js", - "entry": "src/index.ts", + "version": "2.0.0", "types": "build", "deploy": "build", + "main": "build/index.js", "repository": { "type": "git", "url": "https://github.com/wongnai/source-replacement.git" @@ -40,6 +39,7 @@ "semantic-release": "18.0.0", "shake-detector": "0.3.7", "ts-jest": "27.0.7", - "typescript": "4.4.4" + "typescript": "4.4.4", + "vconsole": "3.14.3" } } diff --git a/rollup.config.js b/rollup.config.js index 75d584a..86457e7 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,34 +1,45 @@ import path from 'path' -import commonjs from '@rollup/plugin-commonjs'; +import commonjs from '@rollup/plugin-commonjs' import json from '@rollup/plugin-json' -import resolve from '@rollup/plugin-node-resolve'; +import resolve from '@rollup/plugin-node-resolve' import typescript from '@rollup/plugin-typescript' import visualizer from 'rollup-plugin-visualizer' import config from './package.json' -export default { - input: config.entry, - output: { - dir: config.deploy, - format: 'umd', - sourcemap: true, - name: 'SourceReplacement' - }, - plugins: [ - resolve({ - browser: true, - }), - commonjs(), - typescript({ - declaration: true, - declarationDir: config.deploy, - exclude: 'src/**/*.test.ts', - }), - json(), - visualizer({ - filename: path.resolve(config.deploy, 'stat.html') - }), - ], -}; +const sharedPlugins = [ + resolve({ + browser: true, + }), + commonjs(), + typescript({ + declaration: true, + declarationDir: config.deploy, + exclude: 'src/**/*.test.ts', + }), + json(), + visualizer({ + filename: path.resolve(config.deploy, 'stat.html'), + }), +] + +export default [ + { + input: 'src/executors/code-blocker/index.ts', + output: { + dir: config.deploy, + format: 'cjs', + }, + plugins: sharedPlugins + }, + { + input: 'src/executors/source-replacement/index.ts', + output: { + dir: config.deploy, + format: 'umd', + name: 'SourceReplacement', + }, + plugins: sharedPlugins, + }, +] diff --git a/src/core/preventPerformExistingScriptDuringInjection/index.test.ts b/src/core/preventPerformExistingScriptDuringInjection/index.test.ts new file mode 100644 index 0000000..c6be402 --- /dev/null +++ b/src/core/preventPerformExistingScriptDuringInjection/index.test.ts @@ -0,0 +1,81 @@ +import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' +import preventPerformExistingScriptDuringInjection from '.' + +describe('preventPerformExistingScriptDuringInjection()', () => { + const MOCK_URL = 'http://example.com' + + const promptSpy = jest.spyOn(global, 'prompt').mockReturnValue(MOCK_URL) + + Object.defineProperty(window, 'location', { + value: { + reload: jest.fn(), + }, + }) + + class VConsolePlugin { + events: Record = {} + + on = jest.fn().mockImplementation((key: string, handler: Function) => { + const watcher = jest.fn() + + handler(watcher) + + this.events[key] = watcher.mock.calls[0][0] + }) + } + + class FakeVConsole { + static VConsolePlugin = VConsolePlugin + + plugins: VConsolePlugin[] = [] + + addPlugin = jest.fn().mockImplementation(plugin => { + this.plugins.push(plugin) + }) + } + + window['VConsole'] = FakeVConsole + + window['vConsole'] = new FakeVConsole() + + afterEach(() => { + jest.clearAllMocks() + sessionStorage.clear() + }) + + it('should throw exception if app is marked from session that to be replaced', () => { + sessionStorage.setItem(REPLACEMENT_SOURCE_KEY, MOCK_URL) + + expect(() => preventPerformExistingScriptDuringInjection()).toThrowError( + new Error( + 'The under of code block stop performing causing by replacement process is running', + ), + ) + }) + + it('should init vconsole plugin for set source from prompt to session', () => { + preventPerformExistingScriptDuringInjection() + + const plugin = window['vConsole'].plugins[0] + + expect(window['vConsole'].addPlugin).toBeCalledTimes(1) + expect(window['vConsole'].addPlugin).toBeCalledWith(plugin) + expect(plugin).toBeInstanceOf(VConsolePlugin) + + expect(plugin.events.renderTab).toBe('
Click to Replacement button below
') + + expect(plugin.events.addTool).toHaveLength(1) + + const addToolEvent = plugin.events.addTool[0] + + expect(addToolEvent.name).toBe('Replacement') + + addToolEvent.onClick() + + expect(promptSpy).toBeCalledTimes(1) + expect(promptSpy).toBeCalledWith('Input target replacement') + + expect(sessionStorage.getItem(REPLACEMENT_SOURCE_KEY)).toBe(MOCK_URL) + expect(location.reload).toBeCalledTimes(1) + }) +}) diff --git a/src/core/preventPerformExistingScriptDuringInjection/index.ts b/src/core/preventPerformExistingScriptDuringInjection/index.ts new file mode 100644 index 0000000..687789e --- /dev/null +++ b/src/core/preventPerformExistingScriptDuringInjection/index.ts @@ -0,0 +1,46 @@ +import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' + +import VConsole from 'vconsole' + +const PLUGIN_NAME = 'source_replacement' + +const TAB_NAME = 'Source Replacement' + +function preventPerformExistingScriptDuringInjection() { + if (sessionStorage.getItem(REPLACEMENT_SOURCE_KEY)) { + throw new Error( + 'The under of code block stop performing causing by replacement process is running', + ) + } else { + const Console: typeof VConsole = window['VConsole'] + + const consoleInstance: VConsole = window['vConsole'] + + if (Console && consoleInstance) { + const plugin = new Console.VConsolePlugin(PLUGIN_NAME, TAB_NAME) + + plugin.on('renderTab', callback => { + callback('
Click to Replacement button below
') + }) + + plugin.on('addTool', callback => { + callback([ + { + name: 'Replacement', + onClick: () => { + const targetSource = prompt('Input target replacement') + + sessionStorage.setItem(REPLACEMENT_SOURCE_KEY, targetSource!) + + location.reload() + }, + }, + ]) + }) + + consoleInstance.addPlugin(plugin) + } + } +} + +export default preventPerformExistingScriptDuringInjection diff --git a/src/core/replaceSourceWithTargetSource/index.test.ts b/src/core/replaceSourceWithTargetSource/index.test.ts deleted file mode 100644 index b2a93a3..0000000 --- a/src/core/replaceSourceWithTargetSource/index.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' -import * as replaceSourceWithTargetSourceType from '.' - -describe('replaceSourceWithTargetSource', () => { - const getTargetSourceSpy = jest.fn() - jest.doMock('utils/getTargetSource', () => getTargetSourceSpy) - - const loadTargetSourceSpy = jest.fn() - jest.doMock('utils/loadTargetSource', () => loadTargetSourceSpy) - - const writeSourceToTargetPageSpy = jest.fn() - jest.doMock('utils/writeSourceToTargetPage', () => writeSourceToTargetPageSpy) - - const { default: replaceSourceWithTargetSource } = require('.') as typeof replaceSourceWithTargetSourceType - - afterEach(() => { - sessionStorage.clear() - jest.clearAllMocks() - }) - - it('should not load and write target source if target source is not existed', async () => { - getTargetSourceSpy.mockReturnValue(null) - - await replaceSourceWithTargetSource() - - expect(getTargetSourceSpy).toBeCalledTimes(1) - - expect(loadTargetSourceSpy).not.toBeCalled() - - expect(writeSourceToTargetPageSpy).not.toBeCalled() - }) -}) diff --git a/src/core/replaceSourceWithTargetSource/index.ts b/src/core/replaceSourceWithTargetSource/index.ts deleted file mode 100644 index c141be4..0000000 --- a/src/core/replaceSourceWithTargetSource/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' -import getTargetSource from 'utils/getTargetSource' -import isReplacedPage from 'utils/isReplacedPage' -import loadTargetSource from 'utils/loadTargetSource' -import writeSourceToTargetPage from 'utils/writeSourceToTargetPage' - -async function replaceSourceWithTargetSource() { - const targetSource = getTargetSource() - - if (targetSource && !isReplacedPage(targetSource)) { - console.log('Start replacing with ', targetSource) - - sessionStorage.setItem(REPLACEMENT_SOURCE_KEY, targetSource) - - const source = await loadTargetSource(targetSource) - - writeSourceToTargetPage(source) - } -} - -export default replaceSourceWithTargetSource diff --git a/src/core/startDevTool/index.test.ts b/src/core/startDevTool/index.test.ts deleted file mode 100644 index 3ae90d9..0000000 --- a/src/core/startDevTool/index.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -describe('startDevTool()', () => { - let listenerSpy: Function - - const stopSpy = jest.fn() - - const startSpy = jest.fn() - - const subscribeSpy = jest.fn() - - class MockShakeDetector { - subscribe = subscribeSpy.mockImplementation(listener => { - listenerSpy = listener - - return this - }) - - stop = stopSpy - - start = startSpy - } - - jest.doMock('shake-detector', () => MockShakeDetector) - - const replaceSourceWithTargetSourceSpy = jest.fn() - jest.doMock('core/replaceSourceWithTargetSource', () => replaceSourceWithTargetSourceSpy) - - it('should perform start shake detector with bind listener to set target from prompt then perform source replacement and stop the shake detector', async () => { - const { default: startDevTool } = await import('.') - - startDevTool() - - expect(subscribeSpy).toBeCalledTimes(1) - - expect(startSpy).toBeCalledTimes(1) - - expect(replaceSourceWithTargetSourceSpy).not.toBeCalled() - - expect(stopSpy).not.toBeCalled() - - listenerSpy() - - expect(stopSpy).toBeCalledTimes(1) - }) -}) diff --git a/src/core/startDevTool/index.ts b/src/core/startDevTool/index.ts deleted file mode 100644 index d6c61fb..0000000 --- a/src/core/startDevTool/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import replaceSourceWithTargetSource from 'core/replaceSourceWithTargetSource' -import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' -import ShakeDetector from 'shake-detector' -import isEmpty from 'lodash/isEmpty' - -function startDevTool() { - const shakeDetector = new ShakeDetector({}) - - shakeDetector.subscribe(() => { - const target = prompt('Input your target replacement source url or leave it blank if no replacement wanted') - - sessionStorage.setItem(REPLACEMENT_SOURCE_KEY, target!) - - location.reload() - - shakeDetector.stop() - }).confirmPermissionGranted().start() - - shakeDetector.requestPermission().then(() => shakeDetector.start()) -} - -export default startDevTool diff --git a/src/core/startReplacementProcess/index.test.ts b/src/core/startReplacementProcess/index.test.ts new file mode 100644 index 0000000..c2f0611 --- /dev/null +++ b/src/core/startReplacementProcess/index.test.ts @@ -0,0 +1,99 @@ +describe('startReplacementProcess()', () => { + const MOCK_URL = 'http://example.com' + + const MOCK_SOURCE = '

Test

' + + const consumeSourceTargetSpy = jest.fn() + jest.doMock('utils/consumeSourceTarget', () => consumeSourceTargetSpy) + + const getSourceTargetSpy = jest.fn() + jest.doMock('utils/getSourceTarget', () => getSourceTargetSpy) + + const isReplacedPageSpy = jest.fn() + jest.doMock('utils/isReplacedPage', () => isReplacedPageSpy) + + const loadTargetSourceSpy = jest.fn() + jest.doMock('utils/loadTargetSource', () => loadTargetSourceSpy) + + const loggerSpy = jest.fn() + jest.doMock('utils/logger', () => loggerSpy) + + const writeSourceToTargetPageSpy = jest.fn() + jest.doMock('utils/writeSourceToTargetPage', () => writeSourceToTargetPageSpy) + + const { default: startReplacementProcess } = require('.') as typeof import('.') + + afterEach(() => { + jest.clearAllMocks() + }) + + it('should consume source target and log no replacement happen if no source', async () => { + getSourceTargetSpy.mockReturnValue(null) + + await startReplacementProcess() + + expect(consumeSourceTargetSpy).toBeCalledTimes(1) + + expect(loggerSpy).toBeCalledTimes(1) + + expect(loggerSpy).toBeCalledWith('No replacement happen') + + expect(isReplacedPageSpy).not.toBeCalled() + + expect(loadTargetSourceSpy).not.toBeCalled() + + expect(writeSourceToTargetPageSpy).not.toBeCalled() + }) + + it('should consume source target and log page already replaced if source target already existed in dom', async () => { + getSourceTargetSpy.mockReturnValue(MOCK_URL) + + isReplacedPageSpy.mockReturnValue(true) + + await startReplacementProcess() + + expect(consumeSourceTargetSpy).toBeCalledTimes(1) + + expect(loggerSpy).toBeCalledTimes(1) + + expect(loggerSpy).toBeCalledWith('This page already replaced for http://example.com') + + expect(isReplacedPageSpy).toBeCalledTimes(1) + + expect(isReplacedPageSpy).toBeCalledWith(MOCK_URL) + + expect(loadTargetSourceSpy).not.toBeCalled() + + expect(writeSourceToTargetPageSpy).not.toBeCalled() + }) + + it('should consume source target and load source with write on page with notice user when process start and end', async () => { + getSourceTargetSpy.mockReturnValue(MOCK_URL) + + loadTargetSourceSpy.mockReturnValue(MOCK_SOURCE) + + isReplacedPageSpy.mockReturnValue(false) + + await startReplacementProcess() + + expect(consumeSourceTargetSpy).toBeCalledTimes(1) + + expect(loggerSpy).toBeCalledTimes(2) + + expect(loggerSpy).nthCalledWith(1, 'Start replacing process for http://example.com') + + expect(loggerSpy).nthCalledWith(2, 'Page replaced with http://example.com') + + expect(isReplacedPageSpy).toBeCalledTimes(1) + + expect(isReplacedPageSpy).toBeCalledWith(MOCK_URL) + + expect(loadTargetSourceSpy).toBeCalledTimes(1) + + expect(loadTargetSourceSpy).toBeCalledWith(MOCK_URL) + + expect(writeSourceToTargetPageSpy).toBeCalledTimes(1) + + expect(writeSourceToTargetPageSpy).toBeCalledWith(MOCK_SOURCE) + }) +}) diff --git a/src/core/startReplacementProcess/index.ts b/src/core/startReplacementProcess/index.ts new file mode 100644 index 0000000..208655a --- /dev/null +++ b/src/core/startReplacementProcess/index.ts @@ -0,0 +1,34 @@ +import consumeSourceTarget from 'utils/consumeSourceTarget' +import getSourceTarget from 'utils/getSourceTarget' +import isReplacedPage from 'utils/isReplacedPage' +import loadTargetSource from 'utils/loadTargetSource' +import logger from 'utils/logger' +import writeSourceToTargetPage from 'utils/writeSourceToTargetPage' + +async function startReplacementProcess() { + consumeSourceTarget() + + const sourceTarget = getSourceTarget() + + if (!sourceTarget) { + logger('No replacement happen') + + return + } + + if (isReplacedPage(sourceTarget)) { + logger(`This page already replaced for ${sourceTarget}`) + + return + } + + logger(`Start replacing process for ${sourceTarget}`) + + const source = await loadTargetSource(sourceTarget) + + writeSourceToTargetPage(source) + + logger(`Page replaced with ${sourceTarget}`) +} + +export default startReplacementProcess diff --git a/src/executors/code-blocker/index.test.ts b/src/executors/code-blocker/index.test.ts new file mode 100644 index 0000000..764f2c7 --- /dev/null +++ b/src/executors/code-blocker/index.test.ts @@ -0,0 +1,13 @@ +describe('code-blocker', () => { + const preventPerformExistingScriptDuringInjectionSpy = jest.fn() + jest.doMock( + 'core/preventPerformExistingScriptDuringInjection', + () => preventPerformExistingScriptDuringInjectionSpy, + ) + + it('should prevent perform existing script', async () => { + await import('.') + + expect(preventPerformExistingScriptDuringInjectionSpy).toBeCalledTimes(1) + }) +}) diff --git a/src/executors/code-blocker/index.ts b/src/executors/code-blocker/index.ts new file mode 100644 index 0000000..4c462f1 --- /dev/null +++ b/src/executors/code-blocker/index.ts @@ -0,0 +1,3 @@ +import preventPerformExistingScriptDuringInjection from 'core/preventPerformExistingScriptDuringInjection' + +preventPerformExistingScriptDuringInjection() diff --git a/src/executors/source-replacement/index.test.ts b/src/executors/source-replacement/index.test.ts new file mode 100644 index 0000000..ab5ec05 --- /dev/null +++ b/src/executors/source-replacement/index.test.ts @@ -0,0 +1,19 @@ +describe('source-replacement', () => { + const startReplacementProcessSpy = jest.fn() + jest.doMock('core/startReplacementProcess', () => startReplacementProcessSpy) + + const loggerSpy = jest.fn() + jest.doMock('utils/logger', () => loggerSpy) + + it('should perform start replacement process and warn log notify user not use this in production', async () => { + await import('.') + + expect(startReplacementProcessSpy).toBeCalledTimes(1) + + expect(loggerSpy).toBeCalledTimes(1) + expect(loggerSpy).toBeCalledWith( + '"Source Replacement" script is running, remove this if this is production environment.', + 'warn', + ) + }) +}) diff --git a/src/executors/source-replacement/index.ts b/src/executors/source-replacement/index.ts new file mode 100644 index 0000000..d18f291 --- /dev/null +++ b/src/executors/source-replacement/index.ts @@ -0,0 +1,10 @@ +import startReplacementProcess from 'core/startReplacementProcess' + +import logger from 'utils/logger' + +logger( + '"Source Replacement" script is running, remove this if this is production environment.', + 'warn', +) + +startReplacementProcess() diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index 9d9875b..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -describe('script', () => { - const replaceSourceWithTargetSourceSpy = jest.fn() - jest.doMock('core/replaceSourceWithTargetSource', () => replaceSourceWithTargetSourceSpy) - - const startDevToolSpy = jest.fn() - jest.doMock('core/startDevTool', () => startDevToolSpy) - - it('should perform replace source with target source and start dev tool', async () => { - await import('.') - - expect(replaceSourceWithTargetSourceSpy).toBeCalledTimes(1) - expect(startDevToolSpy).toBeCalledTimes(1) - }) -}) diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 8610686..0000000 --- a/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import replaceSourceWithTargetSource from 'core/replaceSourceWithTargetSource' - -import startDevTool from 'core/startDevTool' - -console.log('Source Replacement is running') - -replaceSourceWithTargetSource() - -startDevTool() diff --git a/src/utils/consumeSourceTarget/index.test.ts b/src/utils/consumeSourceTarget/index.test.ts new file mode 100644 index 0000000..65eea14 --- /dev/null +++ b/src/utils/consumeSourceTarget/index.test.ts @@ -0,0 +1,38 @@ +import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' + +describe('consumeSourceTarget()', () => { + const getReplacementTargetFromHashSpy = jest.fn() + jest.doMock('utils/getReplacementTargetFromHash', () => getReplacementTargetFromHashSpy) + + const loggerSpy = jest.fn() + jest.doMock('utils/logger', () => loggerSpy) + + const { default: consumeSourceTarget } = require('.') as typeof import('.') + + afterEach(() => { + jest.clearAllMocks() + sessionStorage.clear() + }) + + it('should perform set session storage at targetReplacementSource as value from hash then log for notice user', () => { + const MOCK_URL = 'https://example.com' + + getReplacementTargetFromHashSpy.mockReturnValue(MOCK_URL) + + consumeSourceTarget() + + expect(sessionStorage.getItem(REPLACEMENT_SOURCE_KEY)).toBe(MOCK_URL) + + expect(loggerSpy).toBeCalledWith('Consuming new replacement target as https://example.com') + }) + + it('should perform nothing that touch on session storage and logger', () => { + getReplacementTargetFromHashSpy.mockReturnValue(undefined) + + consumeSourceTarget() + + expect(sessionStorage.getItem(REPLACEMENT_SOURCE_KEY)).toBeNull() + + expect(loggerSpy).not.toBeCalled() + }) +}) diff --git a/src/utils/consumeSourceTarget/index.ts b/src/utils/consumeSourceTarget/index.ts new file mode 100644 index 0000000..10023ae --- /dev/null +++ b/src/utils/consumeSourceTarget/index.ts @@ -0,0 +1,15 @@ +import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' +import getReplacementTargetFromHash from 'utils/getReplacementTargetFromHash' +import logger from 'utils/logger' + +function consumeSourceTarget() { + const sourceTargetFromHash = getReplacementTargetFromHash() + + if (sourceTargetFromHash) { + sessionStorage.setItem(REPLACEMENT_SOURCE_KEY, sourceTargetFromHash) + + logger(`Consuming new replacement target as ${sourceTargetFromHash}`) + } +} + +export default consumeSourceTarget diff --git a/src/utils/getReplacementTargetFromHash/index.test.ts b/src/utils/getReplacementTargetFromHash/index.test.ts new file mode 100644 index 0000000..bcb2c41 --- /dev/null +++ b/src/utils/getReplacementTargetFromHash/index.test.ts @@ -0,0 +1,19 @@ +import getReplacementTargetFromHash from '.' + +describe('getReplacementTargetFromHash()', () => { + it('should return undefined if no hash', () => { + expect(getReplacementTargetFromHash()).toBeUndefined() + }) + + it('should return undefined if hash existed but no targetReplacementSource in hash', () => { + location.hash = '#otherHash' + + expect(getReplacementTargetFromHash()).toBeUndefined() + }) + + it('should return url from targetReplacementSource in hash if existed', () => { + location.hash = '#targetReplacementSource=https://example.com' + + expect(getReplacementTargetFromHash()).toBe('https://example.com') + }) +}) diff --git a/src/utils/getReplacementTargetFromHash/index.ts b/src/utils/getReplacementTargetFromHash/index.ts new file mode 100644 index 0000000..7d284ba --- /dev/null +++ b/src/utils/getReplacementTargetFromHash/index.ts @@ -0,0 +1,14 @@ +import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' +import qs from 'qs' + +function getReplacementTargetFromHash() { + const { hash } = location + + if (!hash) { + return undefined + } + + return qs.parse(hash.replace('#', ''))[REPLACEMENT_SOURCE_KEY] as string | undefined +} + +export default getReplacementTargetFromHash diff --git a/src/utils/getSourceTarget/index.test.ts b/src/utils/getSourceTarget/index.test.ts new file mode 100644 index 0000000..606e425 --- /dev/null +++ b/src/utils/getSourceTarget/index.test.ts @@ -0,0 +1,13 @@ +import getSourceTarget from '.' + +describe('getSourceTarget()', () => { + it('should return null no replacementTarget in hash and sessionStorage', () => { + expect(getSourceTarget()).toBeNull() + }) + + it('should return value from sessionStorage if existed', () => { + sessionStorage.setItem('targetReplacementSource', 'https://example-storage.com') + + expect(getSourceTarget()).toBe('https://example-storage.com') + }) +}) diff --git a/src/utils/getSourceTarget/index.ts b/src/utils/getSourceTarget/index.ts new file mode 100644 index 0000000..214252b --- /dev/null +++ b/src/utils/getSourceTarget/index.ts @@ -0,0 +1,7 @@ +import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' + +function getSourceTarget() { + return sessionStorage.getItem(REPLACEMENT_SOURCE_KEY) +} + +export default getSourceTarget diff --git a/src/utils/getTargetSource/index.test.ts b/src/utils/getTargetSource/index.test.ts deleted file mode 100644 index 0e750ee..0000000 --- a/src/utils/getTargetSource/index.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import getTargetSource from '.' - -describe('getTargetSource', () => { - it('should return null no replacementTarget in hash and sessionStorage', () => { - expect(getTargetSource()).toBeNull() - }) - - it('should return value from replacementTarget in hash if existed', () => { - Object.defineProperty(window, 'location', { - value: { - hash: '#replacementTarget=https%3A%2F%2Fexample.com', - }, - writable: true, - }) - expect(getTargetSource()).toBe('https://example.com') - }) - - it('should return value from sessionStorage if existed', () => { - Object.defineProperty(window, 'location', { - value: { - hash: '', - }, - writable: true, - }) - - sessionStorage.setItem('targetReplacementSource', 'https://example-storage.com') - - expect(getTargetSource()).toBe('https://example-storage.com') - }) -}) diff --git a/src/utils/getTargetSource/index.ts b/src/utils/getTargetSource/index.ts deleted file mode 100644 index 5abde8d..0000000 --- a/src/utils/getTargetSource/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import qs from 'qs' - -import { REPLACEMENT_SOURCE_KEY } from 'debugConstants' - -function getTargetSource() { - try { - const targetSource = qs.parse(location.hash.replace('#', '')).replacementTarget || sessionStorage.getItem(REPLACEMENT_SOURCE_KEY) - - return targetSource as string - } catch { - return null - } -} - -export default getTargetSource diff --git a/src/utils/isReplacedPage/index.test.ts b/src/utils/isReplacedPage/index.test.ts index 85e451b..d8903de 100644 --- a/src/utils/isReplacedPage/index.test.ts +++ b/src/utils/isReplacedPage/index.test.ts @@ -1,25 +1,25 @@ -import isReplacedPage from "." +import isReplacedPage from '.' describe('isReplacedPage()', () => { - const MOCK_URL = 'https://src' + const MOCK_URL = 'https://src' - it('should return false if no script', () => { - expect(isReplacedPage(MOCK_URL)).toBe(false) - }) + it('should return false if no script', () => { + expect(isReplacedPage(MOCK_URL)).toBe(false) + }) - it('should return false if non of script start with target src', () => { - const script = document.createElement('script') - script.src = 'https://not' - document.body.append(script) + it('should return false if non of script start with target src', () => { + const script = document.createElement('script') + script.src = 'https://not' + document.body.append(script) - expect(isReplacedPage(MOCK_URL)).toBe(false) - }) - - it('should return true if some of script start with target src', () => { - const script = document.createElement('script') - script.src = MOCK_URL - document.body.append(script) + expect(isReplacedPage(MOCK_URL)).toBe(false) + }) - expect(isReplacedPage(MOCK_URL)).toBe(true) - }) + it('should return true if some of script start with target src', () => { + const script = document.createElement('script') + script.src = MOCK_URL + document.body.append(script) + + expect(isReplacedPage(MOCK_URL)).toBe(true) + }) }) diff --git a/src/utils/isReplacedPage/index.ts b/src/utils/isReplacedPage/index.ts index fafd0a9..45dbf3a 100644 --- a/src/utils/isReplacedPage/index.ts +++ b/src/utils/isReplacedPage/index.ts @@ -1,5 +1,7 @@ function isReplacedPage(targetSource: string) { - return [...document.getElementsByTagName('script')].some(script => script.src.startsWith(targetSource)) + return [...document.getElementsByTagName('script')].some(script => + script.src.startsWith(targetSource), + ) } export default isReplacedPage diff --git a/src/utils/loadTargetSource/index.test.ts b/src/utils/loadTargetSource/index.test.ts index 6877aa6..d110a96 100644 --- a/src/utils/loadTargetSource/index.test.ts +++ b/src/utils/loadTargetSource/index.test.ts @@ -1,22 +1,22 @@ -describe('loadTargetSource', () => { - const placeLoadingPlaceholderSpy = jest.fn() - jest.doMock('utils/placeLoadingPlaceholder', () => placeLoadingPlaceholderSpy) +describe('loadTargetSource()', () => { + const placeLoadingPlaceholderSpy = jest.fn() + jest.doMock('utils/placeLoadingPlaceholder', () => placeLoadingPlaceholderSpy) - it('should perform place loading and load source from target', async () => { - const MOCK_SOURCE = '' - const fetchSpy = jest.fn() - Object.defineProperty(window, 'fetch', { - value: fetchSpy.mockReturnValue({ - text: () => MOCK_SOURCE, - }) - }) + it('should perform place loading and load source from target', async () => { + const MOCK_SOURCE = '' + const fetchSpy = jest.fn() + Object.defineProperty(window, 'fetch', { + value: fetchSpy.mockReturnValue({ + text: () => MOCK_SOURCE, + }), + }) - const { default: loadTargetSource } = await import('.') + const { default: loadTargetSource } = await import('.') - const source = await loadTargetSource('https://example.com') + const source = await loadTargetSource('https://example.com') - expect(fetchSpy).toBeCalledWith('https://example.com') - expect(placeLoadingPlaceholderSpy).toBeCalledTimes(1) - expect(source).toEqual(MOCK_SOURCE) - }) + expect(fetchSpy).toBeCalledWith('https://example.com') + expect(placeLoadingPlaceholderSpy).toBeCalledTimes(1) + expect(source).toEqual(MOCK_SOURCE) + }) }) diff --git a/src/utils/loadTargetSource/index.ts b/src/utils/loadTargetSource/index.ts index 5a3e9e3..968a1c5 100644 --- a/src/utils/loadTargetSource/index.ts +++ b/src/utils/loadTargetSource/index.ts @@ -1,11 +1,11 @@ import placeLoadingPlaceholder from 'utils/placeLoadingPlaceholder' async function loadTargetSource(targetSourceUrl: string) { - placeLoadingPlaceholder(targetSourceUrl) + placeLoadingPlaceholder(targetSourceUrl) - const response = await fetch(targetSourceUrl) + const response = await fetch(targetSourceUrl) - return response.text() + return response.text() } export default loadTargetSource diff --git a/src/utils/logger/index.test.ts b/src/utils/logger/index.test.ts new file mode 100644 index 0000000..44b7927 --- /dev/null +++ b/src/utils/logger/index.test.ts @@ -0,0 +1,35 @@ +import logger from '.' + +describe('logger()', () => { + const warnSpy = jest.spyOn(console, 'warn') + + const logSpy = jest.spyOn(console, 'log') + + afterEach(() => { + jest.clearAllMocks() + }) + + it('should perform log with prefix when logger called with message and level as log', () => { + logger('test', 'log') + + expect(logSpy).toBeCalledWith('source-replacement: test') + + expect(warnSpy).not.toBeCalled() + }) + + it('should perform log with prefix when logger called with message without level', () => { + logger('test') + + expect(logSpy).toBeCalledWith('source-replacement: test') + + expect(warnSpy).not.toBeCalled() + }) + + it('should perform warn with prefix when logger called with message and level as warn', () => { + logger('test', 'warn') + + expect(warnSpy).toBeCalledWith('source-replacement: test') + + expect(logSpy).not.toBeCalled() + }) +}) diff --git a/src/utils/logger/index.ts b/src/utils/logger/index.ts new file mode 100644 index 0000000..e64afaf --- /dev/null +++ b/src/utils/logger/index.ts @@ -0,0 +1,7 @@ +const LOGGER_PREFIX = 'source-replacement: ' + +function logger(message: string, level: 'warn' | 'log' = 'log') { + console[level](LOGGER_PREFIX + message) +} + +export default logger diff --git a/src/utils/placeLoadingPlaceholder/index.test.ts b/src/utils/placeLoadingPlaceholder/index.test.ts index 51a47ee..03e11b2 100644 --- a/src/utils/placeLoadingPlaceholder/index.test.ts +++ b/src/utils/placeLoadingPlaceholder/index.test.ts @@ -1,13 +1,15 @@ -describe('placeLoadingPlaceholder', () => { - const writeSourceToTargetPageSpy = jest.fn() +describe('placeLoadingPlaceholder()', () => { + const writeSourceToTargetPageSpy = jest.fn() - jest.doMock('utils/writeSourceToTargetPage', () => writeSourceToTargetPageSpy) + jest.doMock('utils/writeSourceToTargetPage', () => writeSourceToTargetPageSpy) - it('should write loading source to target page', async () => { - const { default: placeLoadingPlaceholder } = await import('.') + it('should write loading source to target page', async () => { + const { default: placeLoadingPlaceholder } = await import('.') - placeLoadingPlaceholder('http://example.com') + placeLoadingPlaceholder('http://example.com') - expect(writeSourceToTargetPageSpy).toBeCalledWith('

LMWN Debugger Replacing In Progress...

Target Replacement is http://example.com

') - }) + expect(writeSourceToTargetPageSpy).toBeCalledWith( + '

LMWN Debugger Replacing In Progress...

Target Replacement is http://example.com

', + ) + }) }) diff --git a/src/utils/placeLoadingPlaceholder/index.ts b/src/utils/placeLoadingPlaceholder/index.ts index b8988e4..564987d 100644 --- a/src/utils/placeLoadingPlaceholder/index.ts +++ b/src/utils/placeLoadingPlaceholder/index.ts @@ -1,9 +1,9 @@ import writeSourceToTargetPage from 'utils/writeSourceToTargetPage' function placeLoadingPlaceholder(target: string) { - const LOADING_PLACEHOLDER = `

LMWN Debugger Replacing In Progress...

Target Replacement is ${target}

` + const LOADING_PLACEHOLDER = `

LMWN Debugger Replacing In Progress...

Target Replacement is ${target}

` - writeSourceToTargetPage(LOADING_PLACEHOLDER) + writeSourceToTargetPage(LOADING_PLACEHOLDER) } export default placeLoadingPlaceholder diff --git a/src/utils/writeSourceToTargetPage/index.test.ts b/src/utils/writeSourceToTargetPage/index.test.ts index 7e1dc6a..dbac7c2 100644 --- a/src/utils/writeSourceToTargetPage/index.test.ts +++ b/src/utils/writeSourceToTargetPage/index.test.ts @@ -1,18 +1,18 @@ import writeSourceToTargetPage from '.' -describe('writeSourceToTargetPage', () => { - it('should open document for write source then close', () => { - // @ts-ignore - const openSpy = jest.spyOn(document, 'open').mockImplementation(() => {}) - const writeSpy = jest.spyOn(document, 'write').mockImplementation(() => {}) - const closeSpy = jest.spyOn(document, 'close').mockImplementation(() => {}) +describe('writeSourceToTargetPage()', () => { + it('should open document for write source then close', () => { + // @ts-ignore + const openSpy = jest.spyOn(document, 'open').mockImplementation(() => {}) + const writeSpy = jest.spyOn(document, 'write').mockImplementation(() => {}) + const closeSpy = jest.spyOn(document, 'close').mockImplementation(() => {}) - const MOCK_SOURCE = '' + const MOCK_SOURCE = '' - writeSourceToTargetPage(MOCK_SOURCE) + writeSourceToTargetPage(MOCK_SOURCE) - expect(openSpy).toBeCalledTimes(1) - expect(writeSpy).toBeCalledWith(MOCK_SOURCE) - expect(closeSpy).toBeCalledTimes(1) - }) + expect(openSpy).toBeCalledTimes(1) + expect(writeSpy).toBeCalledWith(MOCK_SOURCE) + expect(closeSpy).toBeCalledTimes(1) + }) }) diff --git a/src/utils/writeSourceToTargetPage/index.ts b/src/utils/writeSourceToTargetPage/index.ts index adac0b5..8a0cce1 100644 --- a/src/utils/writeSourceToTargetPage/index.ts +++ b/src/utils/writeSourceToTargetPage/index.ts @@ -1,7 +1,7 @@ function writeSourceToTargetPage(source: string) { - document.open() - document.write(source) - document.close() + document.open() + document.write(source) + document.close() } export default writeSourceToTargetPage diff --git a/yarn.lock b/yarn.lock index 8bed5f2..b473075 100644 --- a/yarn.lock +++ b/yarn.lock @@ -272,6 +272,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/runtime@^7.17.2": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.16.0", "@babel/template@^7.3.3": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" @@ -1835,6 +1842,16 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +copy-text-to-clipboard@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" + integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== + +core-js@^3.1.3, core-js@^3.11.0: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" + integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2332,6 +2349,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.0.0: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-glob@^3.1.1: version "3.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" @@ -3943,7 +3971,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= -lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4222,6 +4250,11 @@ ms@^2.0.0, ms@^2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mutation-observer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/mutation-observer/-/mutation-observer-1.0.3.tgz#42e9222b101bca82e5ba9d5a7acf4a14c0f263d0" + integrity sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA== + mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -5046,6 +5079,11 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + regexpp@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -5173,6 +5211,15 @@ rollup-plugin-includepaths@0.2.4: resolved "https://registry.yarnpkg.com/rollup-plugin-includepaths/-/rollup-plugin-includepaths-0.2.4.tgz#eb21d2d05ad410856a7c2f0612a85983fd11cc62" integrity sha512-iZen+XKVExeCzk7jeSZPJKL7B67slZNr8GXSC5ROBXtDGXDBH8wdjMfdNW5hf9kPt+tHyIvWh3wlE9bPrZL24g== +rollup-plugin-multi-input@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-multi-input/-/rollup-plugin-multi-input-1.3.1.tgz#07b903b618c005871fea1bd0c4efae7d1aac4fa1" + integrity sha512-bPsxHR6dUney7zsCAAlfkq7lbuy5xph2CvUstSv88oqhtRiLWXwVjiA1Gb4HVjC6I9sJI2eZeQlozXa+GXJKDA== + dependencies: + core-js "^3.1.3" + fast-glob "^3.0.0" + lodash "^4.17.11" + rollup-plugin-visualizer@5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.5.0.tgz#dbe9daa3a46576fb697eb62b19ed251112b85d1e" @@ -6006,6 +6053,16 @@ validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0: dependencies: builtins "^1.0.3" +vconsole@3.14.3: + version "3.14.3" + resolved "https://registry.yarnpkg.com/vconsole/-/vconsole-3.14.3.tgz#a452867f03b2eb1f57a3ce4b8ff2b0e067e92040" + integrity sha512-Je26lm4AzS8uGRVLvHRmCK2MoSDviM/z/kpM4RLGrbDzDB36stlMjmtOa3Vh9IQDQG/aw/gqQGtpSHIGEP7/og== + dependencies: + "@babel/runtime" "^7.17.2" + copy-text-to-clipboard "^3.0.1" + core-js "^3.11.0" + mutation-observer "^1.0.3" + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"