Skip to content

Commit

Permalink
feat: new API WebExtensionMessage and getExtensionEnvironment (#298)
Browse files Browse the repository at this point in the history
* chore: prototype of new API

* chore: add asyncIterator demo

* chore: adjust api

* chore: redesign api

* chore: resolve review

* refactor: remove regenerator-runtime as dep

* test: add test extension

* chore: not emit dts in umd/ folder

* test: update test extension

* tests: update test-extension

* feat: new API of getting environments

* feat: change some name in envs, add shortcuts

* feat: new API WebExtensionMessage

* fix: logic bug of sending messages

* test: add test page

* chore: remove async iter on class instance of webext msg
  • Loading branch information
Jack-Works authored Nov 5, 2020
1 parent c4a811b commit fdfa48b
Show file tree
Hide file tree
Showing 21 changed files with 682 additions and 45 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
/temp
.rpt2_cache
*.tsbuildinfo
test-extension/kit.*

# dependencies
/node_modules
node_modules

# testing
/coverage
Expand Down
4 changes: 1 addition & 3 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
/.vscode
/.circleci

# use less build output
/umd/**/*.ts
/umd/**/*.ts.map
/test-extension

# can be regenerated
/api-documents
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"dependencies": {
"@servie/events": "^1.0.0",
"async-call-rpc": "^4.1.0",
"concurrent-lock": "^1.0.7",
"event-iterator": "^2.0.0",
"jsx-jsonml-devtools-renderer": "^1.4.3",
"lodash-es": "^4.17.15",
"memorize-decorator": "^0.2.2",
Expand All @@ -64,6 +64,7 @@
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.1",
"rollup": "^2.30.0",
"rollup-plugin-analyzer": "^3.3.0",
"rollup-plugin-typescript2": "^0.28.0",
"ts-jest": "^26.4.3",
"ts-node": "^9.0.0",
Expand Down
32 changes: 14 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 16 additions & 6 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import typescript from 'rollup-plugin-typescript2'
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
// import analyze from 'rollup-plugin-analyzer'
// import { writeFileSync } from 'fs'

function parseMaybe(s) {
return typeof s === 'string' ? JSON.parse(s) : {}
}

const config = {
input: './src/index.ts',
output: {
file: './umd/index.cjs',
format: 'umd',
name: 'HoloflowsKit',
},
output: [
{ file: './umd/index.cjs', format: 'umd', name: 'HoloflowsKit' },
{ file: './test-extension/kit.js', format: 'umd', name: 'HoloflowsKit' },
],
plugins: [
nodeResolve({
browser: true,
Expand All @@ -25,7 +26,12 @@ const config = {
}),
typescript({
tsconfigOverride: {
compilerOptions: { target: 'ES2018', ...parseMaybe(process.env.TS_OPTS) },
compilerOptions: {
target: 'ES2018',
declaration: false,
declarationMap: false,
...parseMaybe(process.env.TS_OPTS),
},
},
}),
replace({
Expand All @@ -35,6 +41,10 @@ const config = {
extensions: ['.js', '.ts', '.tsx'],
exclude: ['node_modules/lodash-es/'],
}),
// analyze({
// writeTo: (k) => writeFileSync('./out.log', k),
// filter: (x) => !x.id.includes('lodash') && !x.id.includes('src/'),
// }),
],
}

Expand Down
5 changes: 1 addition & 4 deletions src/DOM/Watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ import { DOMProxy, DOMProxyOptions } from './Proxy'
import { Emitter, EventListener } from '@servie/events'
import { LiveSelector } from './LiveSelector'

import differenceWith from 'lodash-es/differenceWith'
import intersectionWith from 'lodash-es/intersectionWith'
import uniqWith from 'lodash-es/uniqWith'
import { Deadline, requestIdleCallback } from '../util/requestIdleCallback'
import { isNil } from 'lodash-es'
import { isNil, uniqWith, intersectionWith, differenceWith } from 'lodash-es'
import { timeout } from '../util/sleep'
import { WatcherDevtoolsEnhancer } from '../Debuggers/WatcherDevtoolsEnhancer'
import { installCustomObjectFormatter } from 'jsx-jsonml-devtools-renderer'
Expand Down
2 changes: 1 addition & 1 deletion src/Extension/AutomatedTabTask.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { sleep, timeout as timeoutFn } from '../util/sleep'
import { AsyncCall, AsyncCallOptions } from 'async-call-rpc'
import { GetContext } from './Context'
import Lock from 'concurrent-lock'
import Lock from '../util/ConcurrentLock'
import { memorize } from 'memorize-decorator'
import { MessageCenter } from './MessageCenter'

Expand Down
138 changes: 135 additions & 3 deletions src/Extension/Context.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/**
* All context that possible in when developing a WebExtension
* @deprecated, remove in 0.9.0
*/
export type Contexts = 'background' | 'content' | 'webpage' | 'unknown' | 'options' | 'debugging'
/**
* Get current running context.
*
* @deprecated Use getExtensionEnvironment(), remove in 0.9.0
* @remarks
* - background: background script
* - content: content script
Expand All @@ -19,27 +20,125 @@ export function GetContext(): Contexts {
if (scheme || location.hostname === 'localhost') {
if (
backgroundURL === location.href ||
['generated', 'background', 'page', '.html'].every(x => location.pathname.match(x))
['generated', 'background', 'page', '.html'].every((x) => location.pathname.match(x))
)
return 'background'
}
if (scheme) return 'options'
if (browser.runtime?.getManifest !== undefined) return 'content'
}
// What about rollup?
if (location.hostname === 'localhost') return 'debugging'
return 'webpage'
}

/** Current running environment of Web Extension */
export enum Environment {
/** has browser as a global variable */ HasBrowserAPI = 1 << 1,
/** URL protocol ends with "-extension:" */ ExtensionProtocol = 1 << 2,
/** Current running context is Content Script */ ContentScript = 1 << 3,
// userScript = 1 << 4,
/** URL is listed in the manifest.background or generated background page */ ManifestBackground = 1 << 6,
/** URL is listed in the manifest.options_ui */ ManifestOptions = 1 << 7,
/** URL is listed in the manifest.browser_action */ ManifestBrowserAction = 1 << 8,
/** URL is listed in the manifest.page_action */ ManifestPageAction = 1 << 9,
/** URL is listed in the manifest.devtools_page */ ManifestDevTools = 1 << 10,
/** URL is listed in the manifest.sidebar_action. Firefox Only */ ManifestSidebar = 1 << 11,
/** URL is listed in the manifest.chrome_url_overrides.newtab */ ManifestOverridesNewTab = 1 << 12,
/** URL is listed in the manifest.chrome_url_overrides.bookmarks */ ManifestOverridesBookmarks = 1 << 13,
/** URL is listed in the manifest.chrome_url_overrides.history */ ManifestOverridesHistory = 1 << 14,
// DO NOT USE value that bigger than 1 << 20
}
declare const __holoflows_kit_get_environment_debug__: number
let result: Environment
/**
* Get the current running environment
* @remarks You can use the global variable `__holoflows_kit_get_environment_debug__` to overwrite the return value if the current hostname is localhost or 127.0.0.1
*/
export function getExtensionEnvironment(): Environment {
if (result !== undefined) return result
try {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
const val = __holoflows_kit_get_environment_debug__
if (val !== undefined) return Number(val)
}
} catch {}
let flag = 0
// Scheme test
try {
const scheme = location.protocol
if (scheme.endsWith('-extension:')) flag |= Environment.ExtensionProtocol
} catch {}
// Browser API test
if (typeof browser !== 'undefined' && browser !== null) {
flag |= Environment.HasBrowserAPI
if (!(flag & Environment.ExtensionProtocol)) flag |= Environment.ContentScript
else {
try {
const manifest = browser.runtime.getManifest()
const current = location.pathname

const background =
manifest.background?.page || manifest.background_page || '/_generated_background_page.html'
const options = manifest.options_ui?.page || manifest.options_page

if (current === slashSuffix(background)) flag |= Environment.ManifestBackground
// TODO: this property support i18n. What will I get when call browser.runtime.getManifest()?
if (current === slashSuffix(manifest.browser_action?.default_popup))
flag |= Environment.ManifestBrowserAction
if (current === slashSuffix(manifest.sidebar_action?.default_panel)) flag |= Environment.ManifestSidebar
if (current === slashSuffix(options)) flag |= Environment.ManifestOptions
if (current === slashSuffix(manifest.devtools_page)) flag |= Environment.ManifestDevTools
if (current === slashSuffix(manifest.page_action?.default_popup)) flag |= Environment.ManifestPageAction

// TODO: this property support i18n.
const { bookmarks, history, newtab } = manifest.chrome_url_overrides || {}
if (current === slashSuffix(bookmarks)) flag |= Environment.ManifestOverridesBookmarks
if (current === slashSuffix(history)) flag |= Environment.ManifestOverridesHistory
if (current === slashSuffix(newtab)) flag |= Environment.ManifestOverridesNewTab
} catch {}
}
}
return (result = flag)
function slashSuffix(x: string | undefined) {
if (x === undefined) return '_'
if (x[0] !== '/') return '/' + x
else return x
}
}

/**
* Print the Environment bit flag in a human-readable format
* @param e - Printing environment bit flag
*/
export function printExtensionEnvironment(e: Environment = getExtensionEnvironment()) {
const flag: (keyof typeof Environment)[] = []
if (Environment.ContentScript & e) flag.push('ContentScript')
if (Environment.ExtensionProtocol & e) flag.push('ExtensionProtocol')
if (Environment.HasBrowserAPI & e) flag.push('HasBrowserAPI')
if (Environment.ManifestBackground & e) flag.push('ManifestBackground')
if (Environment.ManifestDevTools & e) flag.push('ManifestDevTools')
if (Environment.ManifestOptions & e) flag.push('ManifestOptions')
if (Environment.ManifestPageAction & e) flag.push('ManifestPageAction')
if (Environment.ManifestOverridesBookmarks & e) flag.push('ManifestOverridesBookmarks')
if (Environment.ManifestOverridesHistory & e) flag.push('ManifestOverridesHistory')
if (Environment.ManifestOverridesNewTab & e) flag.push('ManifestOverridesNewTab')
if (Environment.ManifestBrowserAction & e) flag.push('ManifestBrowserAction')
if (Environment.ManifestSidebar & e) flag.push('ManifestSidebar')
return flag.join('|')
}

/**
* Make sure this file only run in wanted context
* @param context - Wanted context or contexts
* @param name - name to throw
* @deprecated Remove in 0.9.0, use assertEnvironment
*/
export function OnlyRunInContext(context: Contexts | Contexts[], name: string): void
/**
* Make sure this file only run in wanted context
* @param context - Wanted context or contexts
* @param throws - set to false, OnlyRunInContext will not throws but return a boolean
* @deprecated Remove in 0.9.0, use isEnvironment
*/
export function OnlyRunInContext(context: Contexts | Contexts[], throws: false): boolean
export function OnlyRunInContext(context: Contexts | Contexts[], name: string | false) {
Expand All @@ -51,3 +150,36 @@ export function OnlyRunInContext(context: Contexts | Contexts[], name: string |
}
return true
}

/**
* Assert the current environment satisfy the expectation
* @param env The expected environment
*/
export function assertEnvironment(env: Environment) {
if (!isEnvironment(env))
throw new TypeError(
`Running in the wrong context, (expected ${printExtensionEnvironment(
env,
)}, actually ${printExtensionEnvironment()})`,
)
}
/**
* Assert the current environment NOT satisfy the rejected flags
* @param env The rejected environment
*/
export function assertNotEnvironment(env: Environment) {
if (getExtensionEnvironment() & env)
throw new TypeError(
`Running in wrong context, (expected not match ${printExtensionEnvironment(
env,
)}, actually ${printExtensionEnvironment()})`,
)
}
/**
* Check if the current environment satisfy the expectation
* @param env The expectation environment
*/
export function isEnvironment(env: Environment) {
const now = getExtensionEnvironment()
return Boolean(env & now)
}
1 change: 1 addition & 0 deletions src/Extension/MessageCenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type InternalMessageType = {
const noop = () => {}
/**
* Send and receive messages in different contexts.
* @deprecated Remove in 0.9.0
*/
export class MessageCenter<ITypedMessages> {
/**
Expand Down
Loading

0 comments on commit fdfa48b

Please sign in to comment.