Skip to content

Instantly share code, notes, and snippets.

@cmcaine
Last active May 16, 2018 13:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmcaine/b8cce27553ac7d0bf54dc77604a6a4d4 to your computer and use it in GitHub Desktop.
Save cmcaine/b8cce27553ac7d0bf54dc77604a6a4d4 to your computer and use it in GitHub Desktop.
Is this as little cruft as possible?
/**
* Class C is loaded in two contexts: content and background. The decorators
* @content and @background replace the decorated function with a call to a
* messaging library if the current context is not the desired context for the
* function.
*
* The intention is that a developer can write code that is split between the
* content and background without having to think too much about where it is:
* calls that pass through messaging just look like regular async function
* calls.
*
* There is only ever one background script, but there are many content scripts.
*
* This all works fine, except I don't want to explicitly specify which content
* script to message when a background function calls a content function (it
* should be the content script that called this background func, or the func
* that called this one, etc).
*
* The underlying messaging API is: tabs.sendMessage(tabId, message) and I can
* pass the tabId to the decorator. **But can I avoid passing the tabId as an
* argument to the decorated function?**
*
* Best I've got so far is that I make a copy or proxy `this` and put the tabId
* on that. But this doesn't solve background -> content calls that cross class
* boundaries.
*/
class C {
@content
foo(x, tabId) {
return this.baz(x + 1, tabId)
}
@content
bar(x, tabId) {
return x * 100
}
@background
baz(x, tabId) {
return this.bar(x + 2, tabId)
}
}
C.foo(1) === 400
// runtime-dispatch.ts
// I renamed cn and bg to content and background for the above. I've also not included
// all the messaging stuff, but all you need to know is that it lets you call functions
// and get their return values in an async way.
import { getContext } from "./webext"
import {
addListener,
attributeCaller,
messageBackground,
messageActiveTab,
} from "../messaging"
function funcDec(func) {
return (proto, name, propDesc) => (propDesc.value = func(propDesc.value))
}
/** Runtime function dispatcher.
Give any function that you want to be available in both foreground and
background to one of background, content, or dispatch. Wrapped functions
must have a name, though this could be changed.
Import the same source file into content and background.
At execution time, each decorated function will be returned as-is or
wrapped with a messaging call to the background/content (on activeTab)
version of the same script.
Limitations:
- Helper functions that should be available on only one side can't be
expressed.
- It's a bit clumsy to write:
`export const foo = d.background(function foo(bar) {`
Advantages:
- Modules that involve a lot of communication between bg and content
will be simpler to read and write.
@param modName: the name of the module we're dispatching for.
*/
export default class Dispatcher {
private context
private ourFuncs = Object.create(null)
constructor(private modName) {
this.context = getContext()
addListener(modName, attributeCaller(this.ourFuncs))
}
background = <T extends Function>(func: T): T => {
return this.dispatch(undefined, func)
}
content = <T extends Function>(func: T): T => {
return this.dispatch(func, undefined)
}
// Decorator versions of the above
bg = funcDec(this.background)
cn = funcDec(this.content)
dispatch<T extends Function>(contentFun: T, backgroundFun: T): T {
if (this.context == "background") {
if (backgroundFun) {
this.ourFuncs[backgroundFun.name] = backgroundFun
return backgroundFun
} else {
return this.wrapContent(contentFun)
}
} else {
if (contentFun) {
this.ourFuncs[contentFun.name] = contentFun
return contentFun
} else {
return this.wrapBackground(backgroundFun)
}
}
}
/* private wrap(func) { */
/* let fn */
/* if (this.context == 'background') { */
/* fn = (...args) => message(this.type, func.name, args) */
/* } else { */
/* fn = (...args) => messageActiveTab(this.type, func.name, args) */
/* } */
/* Object.defineProperty(fn, 'name', {value: func.name}) */
/* return fn */
/* } */
private wrapBackground(func): any {
return (...args) => messageBackground(this.modName, func.name, args)
}
private wrapContent(func): any {
return (...args) => messageActiveTab(this.modName, func.name, args)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment