crann 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +7 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
`npm i crann`
|
|
5
|
+
|
|
6
|
+
Crann synchronizes state in Web Extensions, with full Typescript support.
|
|
7
|
+
|
|
8
|
+
- Minimal size (< 5kb)
|
|
9
|
+
- Syncs state between any context you might want - Content Scripts, Devtools, Sidepanels, Popup etc.
|
|
10
|
+
- Removes the need for a tangled web of message passing
|
|
11
|
+
- React to state! Better coding patterns.
|
|
12
|
+
- Optionally persist any value to storage (local or session) via config.
|
|
13
|
+
|
|
14
|
+
Examples: Coming soon.
|
|
15
|
+
|
|
16
|
+
### First, create a Crann instance
|
|
17
|
+
|
|
18
|
+
Crann needs a service worker to coordinate state and access
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// service worker environment
|
|
22
|
+
import { create } from 'crann'
|
|
23
|
+
|
|
24
|
+
// Create an state with some defaults
|
|
25
|
+
const crann = create({
|
|
26
|
+
active: {default: false} // types are inferred from provided default
|
|
27
|
+
trees: {default: 0}
|
|
28
|
+
name: {default: '', partition: Partition.Instance} // partitioned state will be different for each connected context, so everyone connected except the service worker
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Get the state whenever you like.
|
|
32
|
+
const {active, trees} = crann.get() // Not passing in a key will only get state that is common to all, aka no partitioned state
|
|
33
|
+
const {active, trees, name} = crann.get('instancekey') // Passing in a key will return the common state AND the partitioned state for that instance
|
|
34
|
+
|
|
35
|
+
// subscribe to listen for state changes
|
|
36
|
+
crann.subscribe( (state, changes, key) => {
|
|
37
|
+
const { active, trees, name } = state;
|
|
38
|
+
console.log('Common state: active: ', active, ', trees: ', trees);
|
|
39
|
+
console.log(`Instance state for ${key}: `, name);
|
|
40
|
+
// or just look at changes.
|
|
41
|
+
|
|
42
|
+
// key will be the id of the context that made the state update, or null if the update came from the service worker (here)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Set the state (either for an instance or for the common state)
|
|
46
|
+
crann.set({active: true}) // Will notify all connected contexts that active is now true.
|
|
47
|
+
crann.set({name: 'ContentScript'}, 'instancekey'); // Or set for a particular instance if you like
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Then, connect to your Crann instance from any context you like -- eg. content scripts
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import {connect} from 'crann';
|
|
54
|
+
const {get, set, subscribe} = connect();
|
|
55
|
+
|
|
56
|
+
// Similar to the service worker environment, except we will always be dealing with common state AND our own instance state. No distinction from our point of view.
|
|
57
|
+
const {active, trees, name} = get();
|
|
58
|
+
|
|
59
|
+
// Set the state
|
|
60
|
+
set({name: 'My own name'})
|
|
61
|
+
|
|
62
|
+
// subscribe to listen for a particular item of state changing, or any change
|
|
63
|
+
subscribe((changes) => {
|
|
64
|
+
console.log('Trees changed! new value: ', changes.trees)
|
|
65
|
+
}, ['trees']);
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Finally, Persist state!
|
|
70
|
+
|
|
71
|
+
You can persist items so that they will survive a refresh or context closure.
|
|
72
|
+
|
|
73
|
+
Session state: Will last between page refreshes, generally will reset if the user closes the tab or browser.
|
|
74
|
+
Local state: Will persist long-term (unless the user specifically clears it in browser settings)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Simply add persistence to your config when creating crann in the service-worker
|
|
78
|
+
|
|
79
|
+
const crann = create({
|
|
80
|
+
active: {default: false} // types are inferred from provided default
|
|
81
|
+
trees: {default: 0}
|
|
82
|
+
name: {default: '', partition: Partition.Instance} // partitioned state will be different for each connected context, so everyone connected except the service worker
|
|
83
|
+
timesUsed: {default: 0, Persistence.Local}
|
|
84
|
+
firstOpened: {default: new Date(), Persistence.Session}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// note: Persisted state is Common state by default (shared with all contexts)
|
|
88
|
+
```
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var k=Object.create;var S=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var E=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var U=(o,t)=>{for(var e in t)S(o,e,{get:t[e],enumerable:!0})},h=(o,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of A(t))!R.call(o,n)&&n!==e&&S(o,n,{get:()=>t[n],enumerable:!(i=w(t,n))||i.enumerable});return o};var O=(o,t,e)=>(e=o!=null?k(E(o)):{},h(t||!o||!o.__esModule?S(e,"default",{value:o,enumerable:!0}):e,o)),j=o=>h(S({},"__esModule",{value:!0}),o);var M={};U(M,{Crann:()=>C,Partition:()=>g,Persistence:()=>y,connect:()=>b,create:()=>I});module.exports=j(M);var d=O(require("webextension-polyfill"));var g={Instance:"instance",Common:"common"},y={Session:"session",Local:"local",None:"none"};var D=require("porter-source");function v(o,t){if(o===t)return!0;if(o==null||typeof o!="object"||t==null||typeof t!="object")return!1;let e=Object.keys(o),i=Object.keys(t);if(e.length!==i.length)return!1;e.sort(),i.sort();for(let n=0;n<e.length;n++){let a=e[n];if(a!==i[n]||!this.deepEqual(o[a],t[a]))return!1}return!0}var C=class{constructor(t,e){this.config=t;this.instances=new Map;this.stateChangeListeners=[];this.storagePrefix="crann_";this.post=()=>{};console.log("Crann constructor"),this.defaultInstanceState=this.initializeInstanceDefault(),this.defaultCommonState=this.commonState=this.initializeCommonDefault(),this.hydrate();let[i,n,a,s]=(0,D.source)("crann");this.post=i,a(({key:r,connectionType:m,context:p,location:u})=>{console.log("Crann porter connect",r,m,p,u),this.addInstance(r),s(({key:l,connectionType:c,context:f,location:T})=>{console.log("Crann porter connect",l,c,f,T),this.removeInstance(l)})}),this.storagePrefix=e??this.storagePrefix}async addInstance(t){if(this.instances.has(t))console.warn("Crann instance already exists",t);else{let e={...this.defaultInstanceState};this.instances.set(t,e)}}async removeInstance(t){this.instances.has(t)?this.instances.delete(t):console.warn("Crann instance does not exist",t)}async setCommonState(t){let e={...this.commonState,...t};v(this.commonState,e)||(this.commonState=e,await this.persist(t),this.notify(t))}async setInstanceState(t,e){let i=this.instances.get(t)||this.defaultInstanceState,n={...i,...e};v(i,n)||(this.instances.set(t,n),this.notify(e,t))}async persist(t){for(let e in t||this.commonState){let n=this.config[e].persist||"none",a=t?t[e]:this.commonState[e];switch(n){case"session":await d.default.storage.session.set({[this.storagePrefix+e]:a});break;case"local":await d.default.storage.local.set({[this.storagePrefix+e]:a});break;default:break}}}async clear(){this.commonState=this.defaultCommonState,this.instances.forEach((t,e)=>{this.instances.set(e,this.defaultInstanceState)}),await this.persist(),this.notify({})}subscribe(t){this.stateChangeListeners.push(t)}notify(t,e){let i=e?this.get(e):this.get();this.stateChangeListeners.forEach(n=>n(i,t,e)),e?this.post({action:"stateUpdate",payload:{state:t}},e):this.instances.forEach((n,a)=>{this.post({action:"stateUpdate",payload:{state:t}},a)})}get(t){return t?{...this.commonState,...this.instances.get(t)}:{...this.commonState}}async set(t,e){let i={},n={};for(let a in t){let s=this.config[a];if(s.partition==="instance"){let r=a,m=t;i[r]=m[r]}else if(!s.partition||s.partition===g.Common){let r=a,m=t;n[r]=m[r]}}e&&this.setInstanceState(e,i),this.setCommonState(n)}async hydrate(){let t=await d.default.storage.local.get(null),e=await d.default.storage.session.get(null),i={...t,...e},n={};for(let a in i){let s=this.removePrefix(a);if(this.config.hasOwnProperty(s)){let r=i[s];n[s]=r}}this.commonState={...this.defaultCommonState,...n}}removePrefix(t){return t.startsWith(this.storagePrefix)?t.replace(this.storagePrefix,""):t}initializeInstanceDefault(){let t={};return Object.keys(this.config).forEach(e=>{let i=this.config[e];i.partition==="instance"&&(t[e]=i.default)}),t}initializeCommonDefault(){let t={};return Object.keys(this.config).forEach(e=>{let i=this.config[e];i.partition===g.Common&&(t[e]=i.default)}),t}};function I(o,t){return new C(o,t)}var P=require("porter-source");function b(o,t){let[e,i]=(0,P.connect)({namespace:"crann"}),n=L(o),a=null,s=new Map,r=0;return i({stateUpdate:c=>{a=c.payload.state,n={...n,...a},a&&s.forEach(f=>{(f.keys===void 0||f.keys.some(x=>a.hasOwnProperty(x)))&&f.callback(a)})}}),{get:()=>n,set:c=>{e({action:"setState",payload:{state:c}})},subscribe:(c,f)=>(s.set(++r,{keys:f,callback:c}),r),unsubscribe:c=>{s.delete(c)}}}function L(o){let t={};Object.keys(o).forEach(i=>{let n=o[i];n.partition==="instance"&&(t[i]=n.default)});let e={};return Object.keys(o).forEach(i=>{let n=o[i];n.partition==="instance"&&(e[i]=n.default)}),{...t,...e}}0&&(module.exports={Crann,Partition,Persistence,connect,create});
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts", "../../src/crann.ts", "../../src/model/crann.model.ts", "../../src/utils/deepEqual.ts", "../../src/crannAgent.ts"],
|
|
4
|
+
"sourcesContent": ["export { create, Crann } from './crann';\nexport { connect } from './crannAgent';\nexport { CrannAgent, StateUpdate, State, Partition, Persistence } from './model/crann.model';", "import browser from 'webextension-polyfill';\nimport { DerivedInstanceState, DerivedCommonState, ConfigItem, DerivedState, Partition, CrannAgent, StateSubscriber } from './model/crann.model';\nimport { source, } from 'porter-source';\nimport { deepEqual } from './utils/deepEqual';\nimport { AgentLocation, AgentMetadata } from 'porter-source/dist/types/porter.model';\n\nexport class Crann<TConfig extends Record<string, ConfigItem<any>>> {\n private instances: Map<string, DerivedInstanceState<TConfig>> = new Map();\n private defaultCommonState: DerivedCommonState<TConfig>;\n private defaultInstanceState: DerivedInstanceState<TConfig>;\n private commonState: DerivedCommonState<TConfig>;\n private stateChangeListeners: Array<(state: (DerivedCommonState<TConfig>) | (DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>), changes: Partial<DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>>, key?: string) => void> = [];\n private storagePrefix = 'crann_';\n private post: (message: any, contextOrKey: string, location?: Partial<AgentLocation>) => void = () => { };\n\n constructor(private config: TConfig, storagePrefix?: string) {\n console.log('Crann constructor');\n this.defaultInstanceState = this.initializeInstanceDefault();\n this.defaultCommonState = this.commonState = this.initializeCommonDefault();\n this.hydrate();\n const [post, _setMessages, onConnect, onDisconnect] = source('crann');\n this.post = post;\n onConnect(({ key, connectionType, context, location }) => {\n console.log('Crann porter connect', key, connectionType, context, location);\n this.addInstance(key);\n onDisconnect(({ key, connectionType, context, location }) => {\n console.log('Crann porter connect', key, connectionType, context, location);\n this.removeInstance(key);\n });\n });\n this.storagePrefix = storagePrefix ?? this.storagePrefix;\n }\n\n private async addInstance(key: string): Promise<void> {\n if (!this.instances.has(key)) {\n const initialInstanceState = { ...this.defaultInstanceState } as DerivedInstanceState<TConfig>;\n this.instances.set(key, initialInstanceState);\n } else {\n console.warn('Crann instance already exists', key);\n }\n }\n\n private async removeInstance(key: string): Promise<void> {\n if (this.instances.has(key)) {\n this.instances.delete(key);\n } else {\n console.warn('Crann instance does not exist', key);\n }\n }\n\n public async setCommonState(state: Partial<DerivedCommonState<TConfig>>): Promise<void> {\n const update = { ...this.commonState, ...state };\n if (!deepEqual(this.commonState, update)) {\n this.commonState = update;\n await this.persist(state);\n this.notify(state as Partial<DerivedState<TConfig>>);\n }\n }\n\n public async setInstanceState(key: string, state: Partial<DerivedInstanceState<TConfig>>): Promise<void> {\n const currentState = this.instances.get(key) || this.defaultInstanceState;\n const update = { ...currentState, ...state };\n if (!deepEqual(currentState, update)) {\n this.instances.set(key, update);\n this.notify(state as Partial<DerivedState<TConfig>>, key);\n }\n }\n\n // If we pass in specific state to persist, it only persists that state. \n // Otherwise persists all of the worker state.\n private async persist(state?: Partial<DerivedCommonState<TConfig>>): Promise<void> {\n for (const key in (state || this.commonState)) {\n const item = this.config[key] as ConfigItem<any>;\n const persistence = item.persist || 'none';\n const value = state ? state[key as keyof DerivedCommonState<TConfig>] : this.commonState[key];\n switch (persistence) {\n case 'session':\n await browser.storage.session.set({ [this.storagePrefix + (key as string)]: value });\n break;\n case 'local':\n await browser.storage.local.set({ [this.storagePrefix + (key as string)]: value });\n break;\n default:\n break;\n }\n }\n }\n\n public async clear(): Promise<void> {\n this.commonState = this.defaultCommonState;\n this.instances.forEach((_, key) => {\n this.instances.set(key, this.defaultInstanceState);\n });\n await this.persist();\n this.notify({});\n }\n\n public subscribe(listener: (state: (DerivedCommonState<TConfig>) | (DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>), changes: Partial<DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>>, key?: string) => void): void {\n this.stateChangeListeners.push(listener);\n }\n\n private notify(changes: Partial<DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>>, key?: string): void {\n const state = key ? this.get(key) : this.get();\n this.stateChangeListeners.forEach(listener => listener(state, changes, key));\n if (key) {\n this.post({ action: 'stateUpdate', payload: { state: changes } }, key);\n } else {\n // for every key of this.instances, post the state update to the corresponding key\n this.instances.forEach((_, key) => {\n this.post({ action: 'stateUpdate', payload: { state: changes } }, key);\n });\n }\n }\n\n public get(): DerivedCommonState<TConfig>;\n public get(key: string): DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>;\n public get(key?: string): ((DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>) | DerivedCommonState<TConfig>) {\n if (!key) {\n return { ...this.commonState, ...{} as DerivedInstanceState<TConfig> };\n }\n return { ...this.commonState, ...this.instances.get(key) };\n }\n\n public async set(state: Partial<DerivedCommonState<TConfig>>): Promise<void>\n public async set(state: Partial<DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>>, key: string): Promise<void>\n public async set(state: Partial<DerivedInstanceState<TConfig> | DerivedCommonState<TConfig>>, key?: string): Promise<void> {\n const instance = {} as Partial<DerivedInstanceState<TConfig>>;\n const worker = {} as Partial<DerivedCommonState<TConfig>>;\n\n for (const itemKey in state) {\n const item = this.config[itemKey as keyof TConfig] as ConfigItem<any>;\n if (item.partition === 'instance') {\n const instanceItemKey = itemKey as keyof DerivedInstanceState<TConfig>;\n const instanceState = state as Partial<DerivedInstanceState<TConfig>>;\n instance[instanceItemKey] = instanceState[instanceItemKey];\n } else if (!item.partition || item.partition === Partition.Common) {\n const commonItemKey = itemKey as keyof DerivedCommonState<TConfig>;\n const commonState = state as Partial<DerivedCommonState<TConfig>>;\n worker[commonItemKey] = commonState[commonItemKey]!;\n }\n }\n if (key) this.setInstanceState(key, instance);\n this.setCommonState(worker);\n }\n\n private async hydrate(): Promise<void> {\n const local = await browser.storage.local.get(null);\n const session = await browser.storage.session.get(null);\n const combined = { ...local, ...session };\n const update: Partial<DerivedCommonState<TConfig>> = {}; // Cast update as Partial<DerivedState<TConfig>>\n for (const prefixedKey in combined) {\n const key = this.removePrefix(prefixedKey);\n if (this.config.hasOwnProperty(key)) {\n const value = combined[key];\n update[key as keyof DerivedCommonState<TConfig>] = value;\n }\n }\n this.commonState = { ...this.defaultCommonState, ...update };\n }\n\n private removePrefix(key: string): string {\n if (key.startsWith(this.storagePrefix)) {\n return key.replace(this.storagePrefix, '');\n }\n return key;\n }\n\n private initializeInstanceDefault(): DerivedInstanceState<TConfig> {\n const instanceState: any = {};\n Object.keys(this.config).forEach(key => {\n const item: ConfigItem<any> = this.config[key];\n if (item.partition === 'instance') {\n instanceState[key] = item.default;\n }\n });\n return instanceState;\n }\n\n private initializeCommonDefault(): DerivedCommonState<TConfig> {\n const commonState: any = {};\n Object.keys(this.config).forEach(key => {\n const item: ConfigItem<any> = this.config[key];\n if (item.partition === Partition.Common) {\n commonState[key] = item.default;\n }\n });\n return commonState;\n }\n}\n\n\nexport function create<TConfig extends Record<string, ConfigItem<any>>>(config: TConfig, storagePrefix?: string): Crann<TConfig> {\n return new Crann(config, storagePrefix);\n}\n\n", "import browser from 'webextension-polyfill';\n\nexport const Partition = {\n Instance: 'instance' as const,\n Common: 'common' as const\n};\nexport const Persistence = {\n Session: 'session' as const,\n Local: 'local' as const,\n None: 'none' as const\n};\n\ntype ConfigItem<T> = {\n default: T;\n partition?: typeof Partition[keyof typeof Partition]\n persist?: typeof Persistence[keyof typeof Persistence]\n}\n\n// export type Config = typeof StateConfig;\n\ntype DerivedState<T extends Record<string, ConfigItem<any>>> = {\n [P in keyof T]: T[P]['default'];\n};\ntype DerivedInstanceState<T extends Record<string, ConfigItem<any>>> = {\n [P in keyof T as T[P]['partition'] extends 'instance' ? P : never]: T[P]['default'];\n};\ntype DerivedCommonState<T extends Record<string, ConfigItem<any>>> = {\n [P in keyof T as T[P]['partition'] extends 'common' ? P : never]: T[P]['default'];\n};\n\ntype StateSubscriber<TConfig extends Record<string, ConfigItem<any>>> = {\n keys?: Array<keyof DerivedState<TConfig>>;\n callback: (changes: StateUpdate<TConfig>) => void;\n}\n\ntype CrannAgent<TConfig extends Record<string, ConfigItem<any>>> = {\n get: () => DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>;\n set: (update: StateUpdate<TConfig>) => void;\n subscribe: (callback: (changes: StateUpdate<TConfig>) => void, keys?: Array<keyof TConfig>) => number;\n unsubscribe: (id: number) => void;\n}\n\ntype AgentSubscription<TConfig extends Record<string, ConfigItem<any>>> = {\n (callback: (changes: StateUpdate<TConfig>) => void, key?: keyof DerivedState<TConfig>): number;\n}\n\ntype StateUpdate<TConfig extends Record<string, ConfigItem<any>>> = Partial<DerivedState<TConfig>>;\n\nexport { ConfigItem, DerivedState, DerivedInstanceState, DerivedCommonState, StateSubscriber, CrannAgent, AgentSubscription, StateUpdate, DerivedState as State };\n", "export function deepEqual(a: any, b: any): boolean {\n if (a === b) return true;\n\n if (a == null || typeof a !== 'object' || b == null || typeof b !== 'object') return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) return false;\n\n keysA.sort();\n keysB.sort();\n\n for (let i = 0; i < keysA.length; i++) {\n const key = keysA[i];\n if (key !== keysB[i] || !this.deepEqual(a[key], b[key])) return false;\n }\n return true;\n}", "import { ConfigItem, CrannAgent, DerivedState, StateSubscriber, DerivedInstanceState, DerivedCommonState } from \"./model/crann.model\";\nimport { connect as connectPorter } from 'porter-source'\n\nexport function connect<TConfig extends Record<string, ConfigItem<any>>>(config: TConfig, context?: string): CrannAgent<TConfig> {\n const [post, setMessages] = connectPorter({ namespace: 'crann' });\n let _state = getDerivedState(config);\n let changes: Partial<DerivedState<TConfig>> | null = null;\n const listeners = new Map<number, StateSubscriber<TConfig>>();\n let listenerId = 0;\n setMessages({\n stateUpdate: (message) => {\n changes = message.payload.state;\n _state = { ..._state, ...changes };\n if (!!changes) {\n listeners.forEach(listener => {\n if (listener.keys === undefined) {\n listener.callback(changes!);\n } else {\n const matchFound = listener.keys.some(key => changes!.hasOwnProperty(key))\n matchFound && listener.callback(changes!);\n }\n });\n }\n }\n });\n\n const get = () => _state;\n const set = (newState: Partial<DerivedState<TConfig>>) => {\n post({ action: 'setState', payload: { state: newState } });\n }\n const subscribe = (callback: (changes: Partial<DerivedState<TConfig>>) => void, keys?: Array<keyof DerivedState<TConfig>>): number => {\n listeners.set(++listenerId, { keys, callback });\n return listenerId;\n }\n const unsubscribe = (id: number) => {\n listeners.delete(id);\n }\n return { get, set, subscribe, unsubscribe };\n}\n\nfunction getDerivedState<TConfig extends Record<string, ConfigItem<any>>>(config: TConfig): (DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>) {\n const instanceState = {} as DerivedInstanceState<TConfig>;\n\n Object.keys(config).forEach(key => {\n const item: ConfigItem<any> = config[key];\n if (item.partition === 'instance') {\n instanceState[key as keyof DerivedInstanceState<TConfig>] = item.default;\n }\n });\n\n const commonState = {} as DerivedCommonState<TConfig>;\n Object.keys(config).forEach(key => {\n const item: ConfigItem<any> = config[key];\n if (item.partition === 'instance') {\n commonState[key as keyof DerivedCommonState<TConfig>] = item.default;\n }\n });\n\n return { ...instanceState, ...commonState } as (DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["src_exports", "__export", "Crann", "Partition", "Persistence", "connect", "create", "__toCommonJS", "import_webextension_polyfill", "Partition", "Persistence", "import_porter_source", "deepEqual", "a", "b", "keysA", "keysB", "i", "key", "Crann", "config", "storagePrefix", "post", "_setMessages", "onConnect", "onDisconnect", "key", "connectionType", "context", "location", "initialInstanceState", "state", "update", "deepEqual", "currentState", "persistence", "value", "browser", "_", "listener", "changes", "instance", "worker", "itemKey", "item", "instanceItemKey", "instanceState", "Partition", "commonItemKey", "commonState", "local", "session", "combined", "prefixedKey", "create", "import_porter_source", "connect", "config", "context", "post", "setMessages", "connectPorter", "_state", "getDerivedState", "changes", "listeners", "listenerId", "message", "listener", "key", "newState", "callback", "keys", "id", "instanceState", "item", "commonState"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import d from"webextension-polyfill";var g={Instance:"instance",Common:"common"},h={Session:"session",Local:"local",None:"none"};import{source as y}from"porter-source";function l(a,t){if(a===t)return!0;if(a==null||typeof a!="object"||t==null||typeof t!="object")return!1;let e=Object.keys(a),n=Object.keys(t);if(e.length!==n.length)return!1;e.sort(),n.sort();for(let i=0;i<e.length;i++){let o=e[i];if(o!==n[i]||!this.deepEqual(a[o],t[o]))return!1}return!0}var C=class{constructor(t,e){this.config=t;this.instances=new Map;this.stateChangeListeners=[];this.storagePrefix="crann_";this.post=()=>{};console.log("Crann constructor"),this.defaultInstanceState=this.initializeInstanceDefault(),this.defaultCommonState=this.commonState=this.initializeCommonDefault(),this.hydrate();let[n,i,o,s]=y("crann");this.post=n,o(({key:r,connectionType:m,context:v,location:p})=>{console.log("Crann porter connect",r,m,v,p),this.addInstance(r),s(({key:S,connectionType:c,context:f,location:u})=>{console.log("Crann porter connect",S,c,f,u),this.removeInstance(S)})}),this.storagePrefix=e!=null?e:this.storagePrefix}async addInstance(t){if(this.instances.has(t))console.warn("Crann instance already exists",t);else{let e={...this.defaultInstanceState};this.instances.set(t,e)}}async removeInstance(t){this.instances.has(t)?this.instances.delete(t):console.warn("Crann instance does not exist",t)}async setCommonState(t){let e={...this.commonState,...t};l(this.commonState,e)||(this.commonState=e,await this.persist(t),this.notify(t))}async setInstanceState(t,e){let n=this.instances.get(t)||this.defaultInstanceState,i={...n,...e};l(n,i)||(this.instances.set(t,i),this.notify(e,t))}async persist(t){for(let e in t||this.commonState){let i=this.config[e].persist||"none",o=t?t[e]:this.commonState[e];switch(i){case"session":await d.storage.session.set({[this.storagePrefix+e]:o});break;case"local":await d.storage.local.set({[this.storagePrefix+e]:o});break;default:break}}}async clear(){this.commonState=this.defaultCommonState,this.instances.forEach((t,e)=>{this.instances.set(e,this.defaultInstanceState)}),await this.persist(),this.notify({})}subscribe(t){this.stateChangeListeners.push(t)}notify(t,e){let n=e?this.get(e):this.get();this.stateChangeListeners.forEach(i=>i(n,t,e)),e?this.post({action:"stateUpdate",payload:{state:t}},e):this.instances.forEach((i,o)=>{this.post({action:"stateUpdate",payload:{state:t}},o)})}get(t){return t?{...this.commonState,...this.instances.get(t)}:{...this.commonState}}async set(t,e){let n={},i={};for(let o in t){let s=this.config[o];if(s.partition==="instance"){let r=o,m=t;n[r]=m[r]}else if(!s.partition||s.partition===g.Common){let r=o,m=t;i[r]=m[r]}}e&&this.setInstanceState(e,n),this.setCommonState(i)}async hydrate(){let t=await d.storage.local.get(null),e=await d.storage.session.get(null),n={...t,...e},i={};for(let o in n){let s=this.removePrefix(o);if(this.config.hasOwnProperty(s)){let r=n[s];i[s]=r}}this.commonState={...this.defaultCommonState,...i}}removePrefix(t){return t.startsWith(this.storagePrefix)?t.replace(this.storagePrefix,""):t}initializeInstanceDefault(){let t={};return Object.keys(this.config).forEach(e=>{let n=this.config[e];n.partition==="instance"&&(t[e]=n.default)}),t}initializeCommonDefault(){let t={};return Object.keys(this.config).forEach(e=>{let n=this.config[e];n.partition===g.Common&&(t[e]=n.default)}),t}};function D(a,t){return new C(a,t)}import{connect as I}from"porter-source";function b(a,t){let[e,n]=I({namespace:"crann"}),i=P(a),o=null,s=new Map,r=0;return n({stateUpdate:c=>{o=c.payload.state,i={...i,...o},o&&s.forEach(f=>{(f.keys===void 0||f.keys.some(T=>o.hasOwnProperty(T)))&&f.callback(o)})}}),{get:()=>i,set:c=>{e({action:"setState",payload:{state:c}})},subscribe:(c,f)=>(s.set(++r,{keys:f,callback:c}),r),unsubscribe:c=>{s.delete(c)}}}function P(a){let t={};Object.keys(a).forEach(n=>{let i=a[n];i.partition==="instance"&&(t[n]=i.default)});let e={};return Object.keys(a).forEach(n=>{let i=a[n];i.partition==="instance"&&(e[n]=i.default)}),{...t,...e}}export{C as Crann,g as Partition,h as Persistence,b as connect,D as create};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/crann.ts", "../../src/model/crann.model.ts", "../../src/utils/deepEqual.ts", "../../src/crannAgent.ts"],
|
|
4
|
+
"sourcesContent": ["import browser from 'webextension-polyfill';\nimport { DerivedInstanceState, DerivedCommonState, ConfigItem, DerivedState, Partition, CrannAgent, StateSubscriber } from './model/crann.model';\nimport { source, } from 'porter-source';\nimport { deepEqual } from './utils/deepEqual';\nimport { AgentLocation, AgentMetadata } from 'porter-source/dist/types/porter.model';\n\nexport class Crann<TConfig extends Record<string, ConfigItem<any>>> {\n private instances: Map<string, DerivedInstanceState<TConfig>> = new Map();\n private defaultCommonState: DerivedCommonState<TConfig>;\n private defaultInstanceState: DerivedInstanceState<TConfig>;\n private commonState: DerivedCommonState<TConfig>;\n private stateChangeListeners: Array<(state: (DerivedCommonState<TConfig>) | (DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>), changes: Partial<DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>>, key?: string) => void> = [];\n private storagePrefix = 'crann_';\n private post: (message: any, contextOrKey: string, location?: Partial<AgentLocation>) => void = () => { };\n\n constructor(private config: TConfig, storagePrefix?: string) {\n console.log('Crann constructor');\n this.defaultInstanceState = this.initializeInstanceDefault();\n this.defaultCommonState = this.commonState = this.initializeCommonDefault();\n this.hydrate();\n const [post, _setMessages, onConnect, onDisconnect] = source('crann');\n this.post = post;\n onConnect(({ key, connectionType, context, location }) => {\n console.log('Crann porter connect', key, connectionType, context, location);\n this.addInstance(key);\n onDisconnect(({ key, connectionType, context, location }) => {\n console.log('Crann porter connect', key, connectionType, context, location);\n this.removeInstance(key);\n });\n });\n this.storagePrefix = storagePrefix ?? this.storagePrefix;\n }\n\n private async addInstance(key: string): Promise<void> {\n if (!this.instances.has(key)) {\n const initialInstanceState = { ...this.defaultInstanceState } as DerivedInstanceState<TConfig>;\n this.instances.set(key, initialInstanceState);\n } else {\n console.warn('Crann instance already exists', key);\n }\n }\n\n private async removeInstance(key: string): Promise<void> {\n if (this.instances.has(key)) {\n this.instances.delete(key);\n } else {\n console.warn('Crann instance does not exist', key);\n }\n }\n\n public async setCommonState(state: Partial<DerivedCommonState<TConfig>>): Promise<void> {\n const update = { ...this.commonState, ...state };\n if (!deepEqual(this.commonState, update)) {\n this.commonState = update;\n await this.persist(state);\n this.notify(state as Partial<DerivedState<TConfig>>);\n }\n }\n\n public async setInstanceState(key: string, state: Partial<DerivedInstanceState<TConfig>>): Promise<void> {\n const currentState = this.instances.get(key) || this.defaultInstanceState;\n const update = { ...currentState, ...state };\n if (!deepEqual(currentState, update)) {\n this.instances.set(key, update);\n this.notify(state as Partial<DerivedState<TConfig>>, key);\n }\n }\n\n // If we pass in specific state to persist, it only persists that state. \n // Otherwise persists all of the worker state.\n private async persist(state?: Partial<DerivedCommonState<TConfig>>): Promise<void> {\n for (const key in (state || this.commonState)) {\n const item = this.config[key] as ConfigItem<any>;\n const persistence = item.persist || 'none';\n const value = state ? state[key as keyof DerivedCommonState<TConfig>] : this.commonState[key];\n switch (persistence) {\n case 'session':\n await browser.storage.session.set({ [this.storagePrefix + (key as string)]: value });\n break;\n case 'local':\n await browser.storage.local.set({ [this.storagePrefix + (key as string)]: value });\n break;\n default:\n break;\n }\n }\n }\n\n public async clear(): Promise<void> {\n this.commonState = this.defaultCommonState;\n this.instances.forEach((_, key) => {\n this.instances.set(key, this.defaultInstanceState);\n });\n await this.persist();\n this.notify({});\n }\n\n public subscribe(listener: (state: (DerivedCommonState<TConfig>) | (DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>), changes: Partial<DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>>, key?: string) => void): void {\n this.stateChangeListeners.push(listener);\n }\n\n private notify(changes: Partial<DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>>, key?: string): void {\n const state = key ? this.get(key) : this.get();\n this.stateChangeListeners.forEach(listener => listener(state, changes, key));\n if (key) {\n this.post({ action: 'stateUpdate', payload: { state: changes } }, key);\n } else {\n // for every key of this.instances, post the state update to the corresponding key\n this.instances.forEach((_, key) => {\n this.post({ action: 'stateUpdate', payload: { state: changes } }, key);\n });\n }\n }\n\n public get(): DerivedCommonState<TConfig>;\n public get(key: string): DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>;\n public get(key?: string): ((DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>) | DerivedCommonState<TConfig>) {\n if (!key) {\n return { ...this.commonState, ...{} as DerivedInstanceState<TConfig> };\n }\n return { ...this.commonState, ...this.instances.get(key) };\n }\n\n public async set(state: Partial<DerivedCommonState<TConfig>>): Promise<void>\n public async set(state: Partial<DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>>, key: string): Promise<void>\n public async set(state: Partial<DerivedInstanceState<TConfig> | DerivedCommonState<TConfig>>, key?: string): Promise<void> {\n const instance = {} as Partial<DerivedInstanceState<TConfig>>;\n const worker = {} as Partial<DerivedCommonState<TConfig>>;\n\n for (const itemKey in state) {\n const item = this.config[itemKey as keyof TConfig] as ConfigItem<any>;\n if (item.partition === 'instance') {\n const instanceItemKey = itemKey as keyof DerivedInstanceState<TConfig>;\n const instanceState = state as Partial<DerivedInstanceState<TConfig>>;\n instance[instanceItemKey] = instanceState[instanceItemKey];\n } else if (!item.partition || item.partition === Partition.Common) {\n const commonItemKey = itemKey as keyof DerivedCommonState<TConfig>;\n const commonState = state as Partial<DerivedCommonState<TConfig>>;\n worker[commonItemKey] = commonState[commonItemKey]!;\n }\n }\n if (key) this.setInstanceState(key, instance);\n this.setCommonState(worker);\n }\n\n private async hydrate(): Promise<void> {\n const local = await browser.storage.local.get(null);\n const session = await browser.storage.session.get(null);\n const combined = { ...local, ...session };\n const update: Partial<DerivedCommonState<TConfig>> = {}; // Cast update as Partial<DerivedState<TConfig>>\n for (const prefixedKey in combined) {\n const key = this.removePrefix(prefixedKey);\n if (this.config.hasOwnProperty(key)) {\n const value = combined[key];\n update[key as keyof DerivedCommonState<TConfig>] = value;\n }\n }\n this.commonState = { ...this.defaultCommonState, ...update };\n }\n\n private removePrefix(key: string): string {\n if (key.startsWith(this.storagePrefix)) {\n return key.replace(this.storagePrefix, '');\n }\n return key;\n }\n\n private initializeInstanceDefault(): DerivedInstanceState<TConfig> {\n const instanceState: any = {};\n Object.keys(this.config).forEach(key => {\n const item: ConfigItem<any> = this.config[key];\n if (item.partition === 'instance') {\n instanceState[key] = item.default;\n }\n });\n return instanceState;\n }\n\n private initializeCommonDefault(): DerivedCommonState<TConfig> {\n const commonState: any = {};\n Object.keys(this.config).forEach(key => {\n const item: ConfigItem<any> = this.config[key];\n if (item.partition === Partition.Common) {\n commonState[key] = item.default;\n }\n });\n return commonState;\n }\n}\n\n\nexport function create<TConfig extends Record<string, ConfigItem<any>>>(config: TConfig, storagePrefix?: string): Crann<TConfig> {\n return new Crann(config, storagePrefix);\n}\n\n", "import browser from 'webextension-polyfill';\n\nexport const Partition = {\n Instance: 'instance' as const,\n Common: 'common' as const\n};\nexport const Persistence = {\n Session: 'session' as const,\n Local: 'local' as const,\n None: 'none' as const\n};\n\ntype ConfigItem<T> = {\n default: T;\n partition?: typeof Partition[keyof typeof Partition]\n persist?: typeof Persistence[keyof typeof Persistence]\n}\n\n// export type Config = typeof StateConfig;\n\ntype DerivedState<T extends Record<string, ConfigItem<any>>> = {\n [P in keyof T]: T[P]['default'];\n};\ntype DerivedInstanceState<T extends Record<string, ConfigItem<any>>> = {\n [P in keyof T as T[P]['partition'] extends 'instance' ? P : never]: T[P]['default'];\n};\ntype DerivedCommonState<T extends Record<string, ConfigItem<any>>> = {\n [P in keyof T as T[P]['partition'] extends 'common' ? P : never]: T[P]['default'];\n};\n\ntype StateSubscriber<TConfig extends Record<string, ConfigItem<any>>> = {\n keys?: Array<keyof DerivedState<TConfig>>;\n callback: (changes: StateUpdate<TConfig>) => void;\n}\n\ntype CrannAgent<TConfig extends Record<string, ConfigItem<any>>> = {\n get: () => DerivedCommonState<TConfig> & DerivedInstanceState<TConfig>;\n set: (update: StateUpdate<TConfig>) => void;\n subscribe: (callback: (changes: StateUpdate<TConfig>) => void, keys?: Array<keyof TConfig>) => number;\n unsubscribe: (id: number) => void;\n}\n\ntype AgentSubscription<TConfig extends Record<string, ConfigItem<any>>> = {\n (callback: (changes: StateUpdate<TConfig>) => void, key?: keyof DerivedState<TConfig>): number;\n}\n\ntype StateUpdate<TConfig extends Record<string, ConfigItem<any>>> = Partial<DerivedState<TConfig>>;\n\nexport { ConfigItem, DerivedState, DerivedInstanceState, DerivedCommonState, StateSubscriber, CrannAgent, AgentSubscription, StateUpdate, DerivedState as State };\n", "export function deepEqual(a: any, b: any): boolean {\n if (a === b) return true;\n\n if (a == null || typeof a !== 'object' || b == null || typeof b !== 'object') return false;\n\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n\n if (keysA.length !== keysB.length) return false;\n\n keysA.sort();\n keysB.sort();\n\n for (let i = 0; i < keysA.length; i++) {\n const key = keysA[i];\n if (key !== keysB[i] || !this.deepEqual(a[key], b[key])) return false;\n }\n return true;\n}", "import { ConfigItem, CrannAgent, DerivedState, StateSubscriber, DerivedInstanceState, DerivedCommonState } from \"./model/crann.model\";\nimport { connect as connectPorter } from 'porter-source'\n\nexport function connect<TConfig extends Record<string, ConfigItem<any>>>(config: TConfig, context?: string): CrannAgent<TConfig> {\n const [post, setMessages] = connectPorter({ namespace: 'crann' });\n let _state = getDerivedState(config);\n let changes: Partial<DerivedState<TConfig>> | null = null;\n const listeners = new Map<number, StateSubscriber<TConfig>>();\n let listenerId = 0;\n setMessages({\n stateUpdate: (message) => {\n changes = message.payload.state;\n _state = { ..._state, ...changes };\n if (!!changes) {\n listeners.forEach(listener => {\n if (listener.keys === undefined) {\n listener.callback(changes!);\n } else {\n const matchFound = listener.keys.some(key => changes!.hasOwnProperty(key))\n matchFound && listener.callback(changes!);\n }\n });\n }\n }\n });\n\n const get = () => _state;\n const set = (newState: Partial<DerivedState<TConfig>>) => {\n post({ action: 'setState', payload: { state: newState } });\n }\n const subscribe = (callback: (changes: Partial<DerivedState<TConfig>>) => void, keys?: Array<keyof DerivedState<TConfig>>): number => {\n listeners.set(++listenerId, { keys, callback });\n return listenerId;\n }\n const unsubscribe = (id: number) => {\n listeners.delete(id);\n }\n return { get, set, subscribe, unsubscribe };\n}\n\nfunction getDerivedState<TConfig extends Record<string, ConfigItem<any>>>(config: TConfig): (DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>) {\n const instanceState = {} as DerivedInstanceState<TConfig>;\n\n Object.keys(config).forEach(key => {\n const item: ConfigItem<any> = config[key];\n if (item.partition === 'instance') {\n instanceState[key as keyof DerivedInstanceState<TConfig>] = item.default;\n }\n });\n\n const commonState = {} as DerivedCommonState<TConfig>;\n Object.keys(config).forEach(key => {\n const item: ConfigItem<any> = config[key];\n if (item.partition === 'instance') {\n commonState[key as keyof DerivedCommonState<TConfig>] = item.default;\n }\n });\n\n return { ...instanceState, ...commonState } as (DerivedInstanceState<TConfig> & DerivedCommonState<TConfig>);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["browser", "Partition", "Persistence", "source", "deepEqual", "b", "keysA", "keysB", "key", "Crann", "config", "storagePrefix", "post", "_setMessages", "onConnect", "onDisconnect", "source", "key", "connectionType", "context", "location", "initialInstanceState", "state", "update", "deepEqual", "currentState", "persistence", "value", "browser", "_", "listener", "changes", "instance", "worker", "itemKey", "item", "instanceItemKey", "instanceState", "Partition", "commonItemKey", "commonState", "local", "session", "combined", "prefixedKey", "create", "connectPorter", "connect", "config", "context", "post", "setMessages", "_state", "getDerivedState", "changes", "listeners", "listenerId", "message", "listener", "key", "newState", "callback", "keys", "id", "instanceState", "item", "commonState"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crann",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/cjs/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "node esbuild.config.js",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"web-extension",
|
|
18
|
+
"browser-extension",
|
|
19
|
+
"extension",
|
|
20
|
+
"browser",
|
|
21
|
+
"state",
|
|
22
|
+
"message",
|
|
23
|
+
"chrome",
|
|
24
|
+
"firefox",
|
|
25
|
+
"safari",
|
|
26
|
+
"edge",
|
|
27
|
+
"mv3"
|
|
28
|
+
],
|
|
29
|
+
"author": "Marc O'Cleirigh",
|
|
30
|
+
"license": "ISC",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/jest": "^29.5.12",
|
|
33
|
+
"@types/node": "^22.2.0",
|
|
34
|
+
"@types/webextension-polyfill": "^0.10.7",
|
|
35
|
+
"esbuild": "^0.23.0",
|
|
36
|
+
"esbuild-node-externals": "^1.14.0",
|
|
37
|
+
"jest": "^29.7.0",
|
|
38
|
+
"typescript": "^5.5.4",
|
|
39
|
+
"webextension-polyfill": "^0.12.0"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/moclei/crann"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/moclei/crann/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/moclei/crann#readme",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"porter-source": "^1.0.13"
|
|
51
|
+
}
|
|
52
|
+
}
|