Skip to content

Commit

Permalink
Introduce support for plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
marcospassos committed May 10, 2020
1 parent 2157661 commit 15436b1
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 122 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
}],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-use-before-define": "off",
"arrow-body-style": "off",
"arrow-parens": ["error", "as-needed"],
"class-methods-use-this": "off",
"consistent-return": "off",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The Plug JS provides the building blocks for crafting personalized experiences.
- **Zero configuration.** No setup steps required.
- **No backend necessary.** Deliver personalized experiences from static sites.
- **Fast queries.** Double-digit millisecond latency for real-time evaluations.
- **Fully extensible API.** Easily extend the core functionality via lifecycle hooks.
- **Fully extensible API.** Easily add and share new features via plugins.
- **Type-Safe.** Typescript typings included.

## Installation
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"rollup-min": "rollup -c rollup.min-config.js"
},
"dependencies": {
"@croct-tech/sdk": "0.1.2"
"@croct-tech/sdk": "0.1.3"
},
"devDependencies": {
"@types/jest": "^24.0.15",
Expand Down
208 changes: 208 additions & 0 deletions src/globalPlug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import {
EvaluationFacadeOptions as EvaluationOptions,
ExternalEvent,
ExternalEventPayload,
ExternalEventType,
JsonValue,
Logger,
SdkFacade,
SessionFacade,
TrackerFacade,
UserFacade,
} from '@croct-tech/sdk';
import {Plug, Configuration} from './plug';
import {PluginController} from './plugin';

const PLUGIN_NAMESPACE = 'Plugin';

export class GlobalPlug implements Plug {
private instance?: SdkFacade;

private plugins: {[key: string]: PluginController} = {};

private initialize: {(): void};

private initialized: Promise<void>;

public constructor() {
this.initialized = new Promise(resolve => {
this.initialize = resolve;
});
}

public plug(configuration: Configuration): void {
if (this.instance !== undefined) {
const logger = this.instance.getLogger();

logger.info('Croct is already plugged in.');

return;
}

const {plugins, ...sdkConfiguration} = configuration;
const sdk = SdkFacade.init(sdkConfiguration);

this.instance = sdk;

const logger = this.instance.getLogger();
const pending: Promise<void>[] = [];

for (const plugin of plugins ?? []) {
const pluginName = plugin.getName();

logger.debug(`Initializing plugin "${pluginName}"...`);

const controller = plugin.initialize({
tracker: sdk.tracker,
evaluator: sdk.evaluator,
user: sdk.user,
session: sdk.session,
getLogger: (...namespace: string[]): Logger => {
return sdk.getLogger(PLUGIN_NAMESPACE, pluginName, ...namespace);
},
getTabStorage: (...namespace: string[]): Storage => {
return sdk.getTabStorage(PLUGIN_NAMESPACE, pluginName, ...namespace);
},
getBrowserStorage: (...namespace: string[]): Storage => {
return sdk.getBrowserStorage(PLUGIN_NAMESPACE, pluginName, ...namespace);
},
});

logger.debug(`Plugin "${pluginName}" initialized`);

if (typeof controller !== 'object') {
continue;
}

this.plugins[pluginName] = controller;

if (typeof controller.enable === 'function') {
const promise = controller.enable();

if (!(promise instanceof Promise)) {
logger.debug(`Plugin "${pluginName}" enabled`);

continue;
}

pending.push(
promise
.then(() => logger.debug(`Plugin "${pluginName}" enabled`))
.catch(() => logger.error(`Failed to enable plugin "${pluginName}"`)),
);
}
}

Promise.all(pending).then(() => {
this.initialize();

logger.debug('Initialization complete');
});
}

public get plugged(): Promise<this> {
return this.initialized.then(() => this);
}

public get flushed(): Promise<this> {
return this.tracker.flushed.then(() => this);
}

public get sdk(): SdkFacade {
if (this.instance === undefined) {
throw new Error('Croct is not plugged in.');
}

return this.instance;
}

public get tracker(): TrackerFacade {
return this.sdk.tracker;
}

public get user(): UserFacade {
return this.sdk.user;
}

public get session(): SessionFacade {
return this.sdk.session;
}

public isAnonymous(): boolean {
return this.sdk.context.isAnonymous();
}

public getUserId(): string | null {
return this.sdk.context.getUser();
}

public identify(userId: string): void {
this.sdk.identify(userId);
}

public anonymize(): void {
this.sdk.anonymize();
}

public setToken(token: string): void {
this.sdk.setToken(token);
}

public unsetToken(): void {
this.sdk.unsetToken();
}

public track<T extends ExternalEventType>(type: T, payload: ExternalEventPayload<T>): Promise<ExternalEvent<T>> {
return this.sdk.track(type, payload);
}

public evaluate(expression: string, options: EvaluationOptions = {}): Promise<JsonValue> {
return this.sdk.evaluate(expression, options);
}

public async unplug(): Promise<void> {
if (this.instance === undefined) {
return;
}

const logger = this.sdk.getLogger();
const pending: Promise<void>[] = [];

for (const [pluginName, controller] of Object.entries(this.plugins)) {
if (typeof controller.disable !== 'function') {
continue;
}

logger.debug(`Disabling plugin "${pluginName}"...`);

const promise = controller.disable();

if (!(promise instanceof Promise)) {
logger.debug(`Plugin "${pluginName}" disabled`);

continue;
}

pending.push(
promise
.then(() => logger.debug(`Plugin "${pluginName}" disabled`))
.catch(() => logger.error(`Failed to disable "${pluginName}"`)),
);
}

await Promise.all(pending);

try {
await this.instance.close();
} finally {
delete this.instance;

this.plugins = {};
this.initialized = new Promise(resolve => {
this.initialize = resolve;
});

logger.info('🔌 Croct has been unplugged.');
}
}
}
9 changes: 6 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
SdkFacade as Sdk,
SdkFacadeConfiguration as Configuration,
Logger,
UserFacade,
SessionFacade,
Expand All @@ -19,11 +18,11 @@ import {
EventInfo,
JsonValue,
} from '@croct-tech/sdk';
import {Plug} from './plug';
import {Plug, Configuration} from './plug';
import {Plugin, PluginController, PluginSdk} from './plugin';

export {
Sdk,
Configuration,
UserFacade,
SessionFacade,
Tracker,
Expand All @@ -42,6 +41,10 @@ export {
ExpressionError,
JsonValue,
Plug,
Configuration,
Plugin,
PluginController,
PluginSdk,
};

export {default} from './singleton';
Loading

0 comments on commit 15436b1

Please sign in to comment.