@croct/plug 0.23.0 → 0.24.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.
@@ -2,7 +2,7 @@ import { DynamicContentOptions as DynamicContentOptions$1, StaticContentOptions
2
2
  import { ApiKey } from '@croct/sdk/apiKey';
3
3
  import { Logger } from '@croct/sdk/logging';
4
4
  import { JsonObject, JsonValue } from '@croct/json/mutable';
5
- import { F as FetchResponse } from '../plug-CPsFBGem.cjs';
5
+ import { F as FetchResponse } from '../plug-CIEbOxqZ.cjs';
6
6
  import { SlotContent, VersionedSlotId } from '../slot.cjs';
7
7
  import '@croct/sdk/facade/sessionFacade';
8
8
  import '@croct/sdk/facade/userFacade';
@@ -2,7 +2,7 @@ import { DynamicContentOptions as DynamicContentOptions$1, StaticContentOptions
2
2
  import { ApiKey } from '@croct/sdk/apiKey';
3
3
  import { Logger } from '@croct/sdk/logging';
4
4
  import { JsonObject, JsonValue } from '@croct/json/mutable';
5
- import { F as FetchResponse } from '../plug-aoj2L27K.js';
5
+ import { F as FetchResponse } from '../plug-DV00uIb1.js';
6
6
  import { SlotContent, VersionedSlotId } from '../slot.js';
7
7
  import '@croct/sdk/facade/sessionFacade';
8
8
  import '@croct/sdk/facade/userFacade';
package/api/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { EvaluationOptions, evaluate } from './evaluate.cjs';
2
2
  export { DynamicContentOptions, FetchOptions, StaticContentOptions, fetchContent } from './fetchContent.cjs';
3
- export { F as FetchResponse } from '../plug-CPsFBGem.cjs';
3
+ export { F as FetchResponse } from '../plug-CIEbOxqZ.cjs';
4
4
  import '@croct/sdk/evaluator';
5
5
  import '@croct/sdk/apiKey';
6
6
  import '@croct/sdk/logging';
package/api/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { EvaluationOptions, evaluate } from './evaluate.js';
2
2
  export { DynamicContentOptions, FetchOptions, StaticContentOptions, fetchContent } from './fetchContent.js';
3
- export { F as FetchResponse } from '../plug-aoj2L27K.js';
3
+ export { F as FetchResponse } from '../plug-DV00uIb1.js';
4
4
  import '@croct/sdk/evaluator';
5
5
  import '@croct/sdk/apiKey';
6
6
  import '@croct/sdk/logging';
package/constants.cjs CHANGED
@@ -24,11 +24,11 @@ __export(constants_exports, {
24
24
  PREVIEW_WIDGET_URL: () => PREVIEW_WIDGET_URL
25
25
  });
26
26
  module.exports = __toCommonJS(constants_exports);
27
- const CDN_URL = "https://cdn.croct.io/js/v1/lib/plug.js";
28
- const PLAYGROUND_ORIGIN = "https://play.croct.com";
29
- const PLAYGROUND_CONNECT_URL = "https://play.croct.com/connect.html";
30
- const PREVIEW_WIDGET_ORIGIN = "https://cdn.croct.io";
31
- const PREVIEW_WIDGET_URL = "https://cdn.croct.io/js/v1/lib/plug/widget-0.23.0.html";
27
+ const CDN_URL = "";
28
+ const PLAYGROUND_ORIGIN = "";
29
+ const PLAYGROUND_CONNECT_URL = "";
30
+ const PREVIEW_WIDGET_ORIGIN = "";
31
+ const PREVIEW_WIDGET_URL = "";
32
32
  // Annotate the CommonJS export names for ESM import in node:
33
33
  0 && (module.exports = {
34
34
  CDN_URL,
package/constants.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- declare const CDN_URL = "https://cdn.croct.io/js/v1/lib/plug.js";
2
- declare const PLAYGROUND_ORIGIN = "https://play.croct.com";
3
- declare const PLAYGROUND_CONNECT_URL = "https://play.croct.com/connect.html";
4
- declare const PREVIEW_WIDGET_ORIGIN = "https://cdn.croct.io";
5
- declare const PREVIEW_WIDGET_URL = "https://cdn.croct.io/js/v1/lib/plug/widget-0.23.0.html";
1
+ declare const CDN_URL = "";
2
+ declare const PLAYGROUND_ORIGIN = "";
3
+ declare const PLAYGROUND_CONNECT_URL = "";
4
+ declare const PREVIEW_WIDGET_ORIGIN = "";
5
+ declare const PREVIEW_WIDGET_URL = "";
6
6
 
7
7
  export { CDN_URL, PLAYGROUND_CONNECT_URL, PLAYGROUND_ORIGIN, PREVIEW_WIDGET_ORIGIN, PREVIEW_WIDGET_URL };
package/constants.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- declare const CDN_URL = "https://cdn.croct.io/js/v1/lib/plug.js";
2
- declare const PLAYGROUND_ORIGIN = "https://play.croct.com";
3
- declare const PLAYGROUND_CONNECT_URL = "https://play.croct.com/connect.html";
4
- declare const PREVIEW_WIDGET_ORIGIN = "https://cdn.croct.io";
5
- declare const PREVIEW_WIDGET_URL = "https://cdn.croct.io/js/v1/lib/plug/widget-0.23.0.html";
1
+ declare const CDN_URL = "";
2
+ declare const PLAYGROUND_ORIGIN = "";
3
+ declare const PLAYGROUND_CONNECT_URL = "";
4
+ declare const PREVIEW_WIDGET_ORIGIN = "";
5
+ declare const PREVIEW_WIDGET_URL = "";
6
6
 
7
7
  export { CDN_URL, PLAYGROUND_CONNECT_URL, PLAYGROUND_ORIGIN, PREVIEW_WIDGET_ORIGIN, PREVIEW_WIDGET_URL };
package/constants.js CHANGED
@@ -1,8 +1,8 @@
1
- const CDN_URL = "https://cdn.croct.io/js/v1/lib/plug.js";
2
- const PLAYGROUND_ORIGIN = "https://play.croct.com";
3
- const PLAYGROUND_CONNECT_URL = "https://play.croct.com/connect.html";
4
- const PREVIEW_WIDGET_ORIGIN = "https://cdn.croct.io";
5
- const PREVIEW_WIDGET_URL = "https://cdn.croct.io/js/v1/lib/plug/widget-0.23.0.html";
1
+ const CDN_URL = "";
2
+ const PLAYGROUND_ORIGIN = "";
3
+ const PLAYGROUND_CONNECT_URL = "";
4
+ const PREVIEW_WIDGET_ORIGIN = "";
5
+ const PREVIEW_WIDGET_URL = "";
6
6
  export {
7
7
  CDN_URL,
8
8
  PLAYGROUND_CONNECT_URL,
package/global.d.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Plug } from './plug-CPsFBGem.cjs';
1
+ import { P as Plug, a as PluginFactory } from './plug-CIEbOxqZ.cjs';
2
2
  import '@croct/sdk/facade/sessionFacade';
3
3
  import '@croct/sdk/facade/userFacade';
4
4
  import '@croct/sdk/facade/trackerFacade';
@@ -27,5 +27,6 @@ declare global {
27
27
  interface Window {
28
28
  croct?: Plug;
29
29
  onCroctLoad: CroctCallback | undefined;
30
+ croctPlugins?: Record<string, PluginFactory>;
30
31
  }
31
32
  }
package/global.d.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { P as Plug } from './plug-aoj2L27K.js';
1
+ import { P as Plug, a as PluginFactory } from './plug-DV00uIb1.js';
2
2
  import '@croct/sdk/facade/sessionFacade';
3
3
  import '@croct/sdk/facade/userFacade';
4
4
  import '@croct/sdk/facade/trackerFacade';
@@ -27,5 +27,6 @@ declare global {
27
27
  interface Window {
28
28
  croct?: Plug;
29
29
  onCroctLoad: CroctCallback | undefined;
30
+ croctPlugins?: Record<string, PluginFactory>;
30
31
  }
31
32
  }
package/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { G as GlobalPlug } from './plug-CPsFBGem.cjs';
2
- export { C as Configuration, F as FetchResponse, P as Plug } from './plug-CPsFBGem.cjs';
1
+ import { G as GlobalPlug } from './plug-CIEbOxqZ.cjs';
2
+ export { C as Configuration, F as FetchResponse, P as Plug } from './plug-CIEbOxqZ.cjs';
3
3
  import '@croct/sdk/facade/sessionFacade';
4
4
  import '@croct/sdk/facade/userFacade';
5
5
  import '@croct/sdk/facade/trackerFacade';
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { G as GlobalPlug } from './plug-aoj2L27K.js';
2
- export { C as Configuration, F as FetchResponse, P as Plug } from './plug-aoj2L27K.js';
1
+ import { G as GlobalPlug } from './plug-DV00uIb1.js';
2
+ export { C as Configuration, F as FetchResponse, P as Plug } from './plug-DV00uIb1.js';
3
3
  import '@croct/sdk/facade/sessionFacade';
4
4
  import '@croct/sdk/facade/userFacade';
5
5
  import '@croct/sdk/facade/trackerFacade';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@croct/plug",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "A fully-featured devkit for building natively personalized applications.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -103,4 +103,4 @@
103
103
  "**/*.cts",
104
104
  "**/*.map"
105
105
  ]
106
- }
106
+ }
@@ -15,6 +15,7 @@ import { Tab } from '@croct/sdk/tab';
15
15
  import { CidAssigner } from '@croct/sdk/cid';
16
16
  import { Logger } from '@croct/sdk/logging';
17
17
 
18
+ declare function isReservedPluginName(name: string): boolean;
18
19
  interface PluginSdk {
19
20
  readonly version: string;
20
21
  readonly appId: string;
@@ -32,6 +33,9 @@ interface PluginSdk {
32
33
  getTabStorage(...namespace: string[]): Storage;
33
34
  getBrowserStorage(...namespace: string[]): Storage;
34
35
  }
36
+ declare namespace PluginSdk {
37
+ function register(name: string, factory: PluginFactory): void;
38
+ }
35
39
  interface PluginArguments<T = any> {
36
40
  options: T;
37
41
  sdk: PluginSdk;
@@ -84,6 +88,37 @@ declare class GlobalPlug implements Plug {
84
88
  constructor();
85
89
  extend(name: string, plugin: PluginFactory): void;
86
90
  plug(configuration?: Configuration): void;
91
+ /**
92
+ * Registers plugins declared on the `window.croctPlugins` global registry.
93
+ *
94
+ * Plugins already present are registered eagerly so they are enabled as part
95
+ * of the regular initialization. The registry is then replaced with a proxy
96
+ * that registers and enables any plugin declared after the plug is loaded,
97
+ * making the registration order irrelevant.
98
+ *
99
+ * The proxy traps the captured session: once the plug is unplugged or plugged
100
+ * again, the `this.instance === context.sdk` check turns it inert, so no
101
+ * registry state has to be reset on the instance.
102
+ */
103
+ private watchPluginRegistry;
104
+ /**
105
+ * Registers a plugin factory declared on the global registry.
106
+ *
107
+ * Unlike {@link extend}, conflicts are tolerated so a single malformed or
108
+ * duplicated declaration cannot prevent the plug from initializing. A factory
109
+ * already registered under the same name is silently ignored, allowing the
110
+ * registry to be safely re-scanned across re-plugs.
111
+ *
112
+ * @returns Whether the factory was newly registered.
113
+ */
114
+ private registerExternalPlugin;
115
+ /**
116
+ * Instantiates and enables a single plugin.
117
+ *
118
+ * @returns A promise that resolves once the plugin is enabled, or nothing if
119
+ * the plugin is synchronous, disabled, or could not be initialized.
120
+ */
121
+ private enablePlugin;
87
122
  get initialized(): boolean;
88
123
  get plugged(): Promise<this>;
89
124
  get flushed(): Promise<this>;
@@ -106,4 +141,4 @@ declare class GlobalPlug implements Plug {
106
141
  unplug(): Promise<void>;
107
142
  }
108
143
 
109
- export { type Configuration as C, type FetchResponse as F, GlobalPlug as G, type Plug as P, type Plugin as a, type PluginArguments as b, type PluginFactory as c, type PluginSdk as d, type FetchOptions as e, type PluginConfigurations as f };
144
+ export { type Configuration as C, type FetchResponse as F, GlobalPlug as G, type Plug as P, type PluginFactory as a, type Plugin as b, type PluginArguments as c, PluginSdk as d, type FetchOptions as e, type PluginConfigurations as f, isReservedPluginName as i };
@@ -15,6 +15,7 @@ import { Tab } from '@croct/sdk/tab';
15
15
  import { CidAssigner } from '@croct/sdk/cid';
16
16
  import { Logger } from '@croct/sdk/logging';
17
17
 
18
+ declare function isReservedPluginName(name: string): boolean;
18
19
  interface PluginSdk {
19
20
  readonly version: string;
20
21
  readonly appId: string;
@@ -32,6 +33,9 @@ interface PluginSdk {
32
33
  getTabStorage(...namespace: string[]): Storage;
33
34
  getBrowserStorage(...namespace: string[]): Storage;
34
35
  }
36
+ declare namespace PluginSdk {
37
+ function register(name: string, factory: PluginFactory): void;
38
+ }
35
39
  interface PluginArguments<T = any> {
36
40
  options: T;
37
41
  sdk: PluginSdk;
@@ -84,6 +88,37 @@ declare class GlobalPlug implements Plug {
84
88
  constructor();
85
89
  extend(name: string, plugin: PluginFactory): void;
86
90
  plug(configuration?: Configuration): void;
91
+ /**
92
+ * Registers plugins declared on the `window.croctPlugins` global registry.
93
+ *
94
+ * Plugins already present are registered eagerly so they are enabled as part
95
+ * of the regular initialization. The registry is then replaced with a proxy
96
+ * that registers and enables any plugin declared after the plug is loaded,
97
+ * making the registration order irrelevant.
98
+ *
99
+ * The proxy traps the captured session: once the plug is unplugged or plugged
100
+ * again, the `this.instance === context.sdk` check turns it inert, so no
101
+ * registry state has to be reset on the instance.
102
+ */
103
+ private watchPluginRegistry;
104
+ /**
105
+ * Registers a plugin factory declared on the global registry.
106
+ *
107
+ * Unlike {@link extend}, conflicts are tolerated so a single malformed or
108
+ * duplicated declaration cannot prevent the plug from initializing. A factory
109
+ * already registered under the same name is silently ignored, allowing the
110
+ * registry to be safely re-scanned across re-plugs.
111
+ *
112
+ * @returns Whether the factory was newly registered.
113
+ */
114
+ private registerExternalPlugin;
115
+ /**
116
+ * Instantiates and enables a single plugin.
117
+ *
118
+ * @returns A promise that resolves once the plugin is enabled, or nothing if
119
+ * the plugin is synchronous, disabled, or could not be initialized.
120
+ */
121
+ private enablePlugin;
87
122
  get initialized(): boolean;
88
123
  get plugged(): Promise<this>;
89
124
  get flushed(): Promise<this>;
@@ -106,4 +141,4 @@ declare class GlobalPlug implements Plug {
106
141
  unplug(): Promise<void>;
107
142
  }
108
143
 
109
- export { type Configuration as C, type FetchResponse as F, GlobalPlug as G, type Plug as P, type Plugin as a, type PluginArguments as b, type PluginFactory as c, type PluginSdk as d, type FetchOptions as e, type PluginConfigurations as f };
144
+ export { type Configuration as C, type FetchResponse as F, GlobalPlug as G, type Plug as P, type PluginFactory as a, type Plugin as b, type PluginArguments as c, PluginSdk as d, type FetchOptions as e, type PluginConfigurations as f, isReservedPluginName as i };
package/plug.cjs CHANGED
@@ -26,11 +26,16 @@ var import_validation = require("@croct/sdk/validation");
26
26
  var import_token = require("@croct/sdk/token");
27
27
  var import_sdk = require("@croct/sdk");
28
28
  var import_content = require("@croct/content");
29
+ var import_plugin = require('./plugin.cjs');
29
30
  var import_constants = require('./constants.cjs');
30
31
  var import_preview = require('./plugins/preview/index.cjs');
31
32
  var import_autoTracking = require('./plugins/autoTracking/index.cjs');
32
33
  var import_globalVariable = require('./plugins/globalVariable/index.cjs');
33
34
  const PLUGIN_NAMESPACE = "Plugin";
35
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
36
+ function hasOwn(target, property) {
37
+ return objectHasOwnProperty.call(target, property);
38
+ }
34
39
  function detectAppId() {
35
40
  const script = window.document.querySelector(`script[src^='${import_constants.CDN_URL}']`);
36
41
  if (!(script instanceof HTMLScriptElement)) {
@@ -51,7 +56,10 @@ const _GlobalPlug = class _GlobalPlug {
51
56
  });
52
57
  }
53
58
  extend(name, plugin) {
54
- if (this.pluginFactories[name] !== void 0) {
59
+ if ((0, import_plugin.isReservedPluginName)(name)) {
60
+ throw new Error(`The plugin name "${name}" is reserved and cannot be used.`);
61
+ }
62
+ if (hasOwn(this.pluginFactories, name)) {
55
63
  throw new Error(`Another plugin is already registered with name "${name}".`);
56
64
  }
57
65
  this.pluginFactories[name] = plugin;
@@ -91,76 +99,166 @@ const _GlobalPlug = class _GlobalPlug {
91
99
  'It is strongly recommended omitting the "appId" option when using the application-specific tag as it is detected automatically.'
92
100
  );
93
101
  }
102
+ const configurations = plugins ?? {};
103
+ const context = {
104
+ sdk,
105
+ appId,
106
+ logger
107
+ };
108
+ this.watchPluginRegistry(context, configurations);
94
109
  const pending = [];
95
110
  const defaultEnabledPlugins = Object.fromEntries(
96
111
  Object.keys(this.pluginFactories).map((name) => [name, true])
97
112
  );
98
- for (const [name, options] of Object.entries({ ...defaultEnabledPlugins, ...plugins })) {
99
- logger.debug(`Initializing plugin "${name}"...`);
100
- const factory = this.pluginFactories[name];
101
- if (factory === void 0) {
102
- logger.error(`Plugin "${name}" is not registered.`);
103
- continue;
104
- }
105
- if (typeof options !== "boolean" && (options === null || typeof options !== "object")) {
106
- logger.error(
107
- `Invalid options for plugin "${name}", expected either boolean or object but got ${(0, import_validation.describe)(options)}`
108
- );
109
- continue;
110
- }
111
- if (options === false) {
112
- logger.warn(`Plugin "${name}" is declared but not enabled`);
113
- continue;
114
- }
115
- const args = {
116
- options: options === true ? {} : options,
117
- sdk: {
118
- version: import_sdk.VERSION,
119
- appId,
120
- plug: this,
121
- tracker: sdk.tracker,
122
- evaluator: sdk.evaluator,
123
- user: sdk.user,
124
- session: sdk.session,
125
- tab: sdk.context.getTab(),
126
- userTokenStore: {
127
- getToken: sdk.getToken.bind(sdk),
128
- setToken: sdk.setToken.bind(sdk)
129
- },
130
- previewTokenStore: sdk.previewTokenStore,
131
- cidAssigner: sdk.cidAssigner,
132
- eventManager: sdk.eventManager,
133
- getLogger: (...namespace) => sdk.getLogger(PLUGIN_NAMESPACE, name, ...namespace),
134
- getTabStorage: (...namespace) => sdk.getTabStorage(PLUGIN_NAMESPACE, name, ...namespace),
135
- getBrowserStorage: (...namespace) => sdk.getBrowserStorage(PLUGIN_NAMESPACE, name, ...namespace)
136
- }
137
- };
138
- let plugin;
139
- try {
140
- plugin = factory(args);
141
- } catch (error) {
142
- logger.error(`Failed to initialize plugin "${name}": ${(0, import_error.formatCause)(error)}`);
143
- continue;
113
+ for (const [name, options] of Object.entries({ ...defaultEnabledPlugins, ...configurations })) {
114
+ const promise = this.enablePlugin(name, options, context);
115
+ if (promise instanceof Promise) {
116
+ pending.push(promise);
144
117
  }
145
- logger.debug(`Plugin "${name}" initialized`);
146
- if (typeof plugin !== "object") {
147
- continue;
148
- }
149
- this.plugins[name] = plugin;
150
- const promise = plugin.enable();
151
- if (!(promise instanceof Promise)) {
152
- logger.debug(`Plugin "${name}" enabled`);
153
- continue;
154
- }
155
- pending.push(
156
- promise.then(() => logger.debug(`Plugin "${name}" enabled`)).catch((error) => logger.error(`Failed to enable plugin "${name}": ${(0, import_error.formatCause)(error)}`))
157
- );
158
118
  }
159
119
  Promise.all(pending).then(() => {
160
120
  this.initialize();
161
121
  logger.debug("Initialization complete");
162
122
  });
163
123
  }
124
+ /**
125
+ * Registers plugins declared on the `window.croctPlugins` global registry.
126
+ *
127
+ * Plugins already present are registered eagerly so they are enabled as part
128
+ * of the regular initialization. The registry is then replaced with a proxy
129
+ * that registers and enables any plugin declared after the plug is loaded,
130
+ * making the registration order irrelevant.
131
+ *
132
+ * The proxy traps the captured session: once the plug is unplugged or plugged
133
+ * again, the `this.instance === context.sdk` check turns it inert, so no
134
+ * registry state has to be reset on the instance.
135
+ */
136
+ watchPluginRegistry(context, configurations) {
137
+ const { sdk, logger } = context;
138
+ const registry = { ...window.croctPlugins };
139
+ for (const [name, factory] of Object.entries(registry)) {
140
+ this.registerExternalPlugin(name, factory, logger);
141
+ }
142
+ window.croctPlugins = new Proxy(registry, {
143
+ set: (target, property, factory) => {
144
+ const result = Reflect.set(target, property, factory);
145
+ if (result && this.instance === sdk && typeof property === "string") {
146
+ if (this.registerExternalPlugin(property, factory, logger)) {
147
+ const options = hasOwn(configurations, property) ? configurations[property] : true;
148
+ void this.enablePlugin(property, options, context);
149
+ }
150
+ }
151
+ return result;
152
+ },
153
+ deleteProperty: (target, property) => {
154
+ if (this.instance === sdk && typeof property === "string" && hasOwn(this.pluginFactories, property)) {
155
+ logger.error(
156
+ `Plugin "${property}" cannot be unregistered; it will remain registered until the page is reloaded.`
157
+ );
158
+ }
159
+ return Reflect.deleteProperty(target, property);
160
+ }
161
+ });
162
+ }
163
+ /**
164
+ * Registers a plugin factory declared on the global registry.
165
+ *
166
+ * Unlike {@link extend}, conflicts are tolerated so a single malformed or
167
+ * duplicated declaration cannot prevent the plug from initializing. A factory
168
+ * already registered under the same name is silently ignored, allowing the
169
+ * registry to be safely re-scanned across re-plugs.
170
+ *
171
+ * @returns Whether the factory was newly registered.
172
+ */
173
+ registerExternalPlugin(name, factory, logger) {
174
+ if ((0, import_plugin.isReservedPluginName)(name)) {
175
+ logger.error(`The plugin name "${name}" is reserved and cannot be used, ignoring it.`);
176
+ return false;
177
+ }
178
+ if (typeof factory !== "function") {
179
+ logger.error(`The plugin "${name}" declared globally is not a valid factory, ignoring it.`);
180
+ return false;
181
+ }
182
+ const registered = hasOwn(this.pluginFactories, name) ? this.pluginFactories[name] : void 0;
183
+ if (registered !== void 0) {
184
+ if (registered !== factory) {
185
+ logger.warn(`Plugin "${name}" is already registered, ignoring global registration.`);
186
+ }
187
+ return false;
188
+ }
189
+ this.pluginFactories[name] = factory;
190
+ return true;
191
+ }
192
+ /**
193
+ * Instantiates and enables a single plugin.
194
+ *
195
+ * @returns A promise that resolves once the plugin is enabled, or nothing if
196
+ * the plugin is synchronous, disabled, or could not be initialized.
197
+ */
198
+ enablePlugin(name, options, context) {
199
+ const { sdk, appId, logger } = context;
200
+ if ((0, import_plugin.isReservedPluginName)(name)) {
201
+ logger.error(`The plugin name "${name}" is reserved and cannot be used, ignoring it.`);
202
+ return;
203
+ }
204
+ logger.debug(`Initializing plugin "${name}"...`);
205
+ const factory = hasOwn(this.pluginFactories, name) ? this.pluginFactories[name] : void 0;
206
+ if (factory === void 0) {
207
+ logger.error(`Plugin "${name}" is not registered.`);
208
+ return;
209
+ }
210
+ if (typeof options !== "boolean" && (options === null || typeof options !== "object")) {
211
+ logger.error(
212
+ `Invalid options for plugin "${name}", expected either boolean or object but got ${(0, import_validation.describe)(options)}`
213
+ );
214
+ return;
215
+ }
216
+ if (options === false) {
217
+ logger.warn(`Plugin "${name}" is declared but not enabled`);
218
+ return;
219
+ }
220
+ const args = {
221
+ options: options === true ? {} : options,
222
+ sdk: {
223
+ version: import_sdk.VERSION,
224
+ appId,
225
+ plug: this,
226
+ tracker: sdk.tracker,
227
+ evaluator: sdk.evaluator,
228
+ user: sdk.user,
229
+ session: sdk.session,
230
+ tab: sdk.context.getTab(),
231
+ userTokenStore: {
232
+ getToken: sdk.getToken.bind(sdk),
233
+ setToken: sdk.setToken.bind(sdk)
234
+ },
235
+ previewTokenStore: sdk.previewTokenStore,
236
+ cidAssigner: sdk.cidAssigner,
237
+ eventManager: sdk.eventManager,
238
+ getLogger: (...namespace) => sdk.getLogger(PLUGIN_NAMESPACE, name, ...namespace),
239
+ getTabStorage: (...namespace) => sdk.getTabStorage(PLUGIN_NAMESPACE, name, ...namespace),
240
+ getBrowserStorage: (...namespace) => sdk.getBrowserStorage(PLUGIN_NAMESPACE, name, ...namespace)
241
+ }
242
+ };
243
+ let plugin;
244
+ try {
245
+ plugin = factory(args);
246
+ } catch (error) {
247
+ logger.error(`Failed to initialize plugin "${name}": ${(0, import_error.formatCause)(error)}`);
248
+ return;
249
+ }
250
+ logger.debug(`Plugin "${name}" initialized`);
251
+ if (typeof plugin !== "object") {
252
+ return;
253
+ }
254
+ this.plugins[name] = plugin;
255
+ const promise = plugin.enable();
256
+ if (!(promise instanceof Promise)) {
257
+ logger.debug(`Plugin "${name}" enabled`);
258
+ return;
259
+ }
260
+ return promise.then(() => logger.debug(`Plugin "${name}" enabled`)).catch((error) => logger.error(`Failed to enable plugin "${name}": ${(0, import_error.formatCause)(error)}`));
261
+ }
164
262
  get initialized() {
165
263
  return this.instance !== void 0;
166
264
  }
package/plug.d.cts CHANGED
@@ -7,7 +7,7 @@ import '@croct/sdk/utilityTypes';
7
7
  import '@croct/sdk/trackingEvents';
8
8
  import '@croct/sdk/facade/contentFetcherFacade';
9
9
  import '@croct/sdk/contentFetcher';
10
- export { C as Configuration, e as FetchOptions, F as FetchResponse, G as GlobalPlug, P as Plug, f as PluginConfigurations } from './plug-CPsFBGem.cjs';
10
+ export { C as Configuration, e as FetchOptions, F as FetchResponse, G as GlobalPlug, P as Plug, f as PluginConfigurations } from './plug-CIEbOxqZ.cjs';
11
11
  import './slot.cjs';
12
12
  import '@croct/json/mutable';
13
13
  import '@croct/sdk/token';
package/plug.d.ts CHANGED
@@ -7,7 +7,7 @@ import '@croct/sdk/utilityTypes';
7
7
  import '@croct/sdk/trackingEvents';
8
8
  import '@croct/sdk/facade/contentFetcherFacade';
9
9
  import '@croct/sdk/contentFetcher';
10
- export { C as Configuration, e as FetchOptions, F as FetchResponse, G as GlobalPlug, P as Plug, f as PluginConfigurations } from './plug-aoj2L27K.js';
10
+ export { C as Configuration, e as FetchOptions, F as FetchResponse, G as GlobalPlug, P as Plug, f as PluginConfigurations } from './plug-DV00uIb1.js';
11
11
  import './slot.js';
12
12
  import '@croct/json/mutable';
13
13
  import '@croct/sdk/token';
package/plug.js CHANGED
@@ -4,11 +4,16 @@ import { describe } from "@croct/sdk/validation";
4
4
  import { Token } from "@croct/sdk/token";
5
5
  import { VERSION } from "@croct/sdk";
6
6
  import { loadSlotContent } from "@croct/content";
7
+ import { isReservedPluginName } from "./plugin.js";
7
8
  import { CDN_URL } from "./constants.js";
8
9
  import { factory as previewPluginFactory } from "./plugins/preview/index.js";
9
10
  import { factory as autoTrackingPluginFactory } from "./plugins/autoTracking/index.js";
10
11
  import { factory as globalVariablePluginFactory } from "./plugins/globalVariable/index.js";
11
12
  const PLUGIN_NAMESPACE = "Plugin";
13
+ const objectHasOwnProperty = Object.prototype.hasOwnProperty;
14
+ function hasOwn(target, property) {
15
+ return objectHasOwnProperty.call(target, property);
16
+ }
12
17
  function detectAppId() {
13
18
  const script = window.document.querySelector(`script[src^='${CDN_URL}']`);
14
19
  if (!(script instanceof HTMLScriptElement)) {
@@ -29,7 +34,10 @@ const _GlobalPlug = class _GlobalPlug {
29
34
  });
30
35
  }
31
36
  extend(name, plugin) {
32
- if (this.pluginFactories[name] !== void 0) {
37
+ if (isReservedPluginName(name)) {
38
+ throw new Error(`The plugin name "${name}" is reserved and cannot be used.`);
39
+ }
40
+ if (hasOwn(this.pluginFactories, name)) {
33
41
  throw new Error(`Another plugin is already registered with name "${name}".`);
34
42
  }
35
43
  this.pluginFactories[name] = plugin;
@@ -69,76 +77,166 @@ const _GlobalPlug = class _GlobalPlug {
69
77
  'It is strongly recommended omitting the "appId" option when using the application-specific tag as it is detected automatically.'
70
78
  );
71
79
  }
80
+ const configurations = plugins ?? {};
81
+ const context = {
82
+ sdk,
83
+ appId,
84
+ logger
85
+ };
86
+ this.watchPluginRegistry(context, configurations);
72
87
  const pending = [];
73
88
  const defaultEnabledPlugins = Object.fromEntries(
74
89
  Object.keys(this.pluginFactories).map((name) => [name, true])
75
90
  );
76
- for (const [name, options] of Object.entries({ ...defaultEnabledPlugins, ...plugins })) {
77
- logger.debug(`Initializing plugin "${name}"...`);
78
- const factory = this.pluginFactories[name];
79
- if (factory === void 0) {
80
- logger.error(`Plugin "${name}" is not registered.`);
81
- continue;
82
- }
83
- if (typeof options !== "boolean" && (options === null || typeof options !== "object")) {
84
- logger.error(
85
- `Invalid options for plugin "${name}", expected either boolean or object but got ${describe(options)}`
86
- );
87
- continue;
88
- }
89
- if (options === false) {
90
- logger.warn(`Plugin "${name}" is declared but not enabled`);
91
- continue;
92
- }
93
- const args = {
94
- options: options === true ? {} : options,
95
- sdk: {
96
- version: VERSION,
97
- appId,
98
- plug: this,
99
- tracker: sdk.tracker,
100
- evaluator: sdk.evaluator,
101
- user: sdk.user,
102
- session: sdk.session,
103
- tab: sdk.context.getTab(),
104
- userTokenStore: {
105
- getToken: sdk.getToken.bind(sdk),
106
- setToken: sdk.setToken.bind(sdk)
107
- },
108
- previewTokenStore: sdk.previewTokenStore,
109
- cidAssigner: sdk.cidAssigner,
110
- eventManager: sdk.eventManager,
111
- getLogger: (...namespace) => sdk.getLogger(PLUGIN_NAMESPACE, name, ...namespace),
112
- getTabStorage: (...namespace) => sdk.getTabStorage(PLUGIN_NAMESPACE, name, ...namespace),
113
- getBrowserStorage: (...namespace) => sdk.getBrowserStorage(PLUGIN_NAMESPACE, name, ...namespace)
114
- }
115
- };
116
- let plugin;
117
- try {
118
- plugin = factory(args);
119
- } catch (error) {
120
- logger.error(`Failed to initialize plugin "${name}": ${formatCause(error)}`);
121
- continue;
91
+ for (const [name, options] of Object.entries({ ...defaultEnabledPlugins, ...configurations })) {
92
+ const promise = this.enablePlugin(name, options, context);
93
+ if (promise instanceof Promise) {
94
+ pending.push(promise);
122
95
  }
123
- logger.debug(`Plugin "${name}" initialized`);
124
- if (typeof plugin !== "object") {
125
- continue;
126
- }
127
- this.plugins[name] = plugin;
128
- const promise = plugin.enable();
129
- if (!(promise instanceof Promise)) {
130
- logger.debug(`Plugin "${name}" enabled`);
131
- continue;
132
- }
133
- pending.push(
134
- promise.then(() => logger.debug(`Plugin "${name}" enabled`)).catch((error) => logger.error(`Failed to enable plugin "${name}": ${formatCause(error)}`))
135
- );
136
96
  }
137
97
  Promise.all(pending).then(() => {
138
98
  this.initialize();
139
99
  logger.debug("Initialization complete");
140
100
  });
141
101
  }
102
+ /**
103
+ * Registers plugins declared on the `window.croctPlugins` global registry.
104
+ *
105
+ * Plugins already present are registered eagerly so they are enabled as part
106
+ * of the regular initialization. The registry is then replaced with a proxy
107
+ * that registers and enables any plugin declared after the plug is loaded,
108
+ * making the registration order irrelevant.
109
+ *
110
+ * The proxy traps the captured session: once the plug is unplugged or plugged
111
+ * again, the `this.instance === context.sdk` check turns it inert, so no
112
+ * registry state has to be reset on the instance.
113
+ */
114
+ watchPluginRegistry(context, configurations) {
115
+ const { sdk, logger } = context;
116
+ const registry = { ...window.croctPlugins };
117
+ for (const [name, factory] of Object.entries(registry)) {
118
+ this.registerExternalPlugin(name, factory, logger);
119
+ }
120
+ window.croctPlugins = new Proxy(registry, {
121
+ set: (target, property, factory) => {
122
+ const result = Reflect.set(target, property, factory);
123
+ if (result && this.instance === sdk && typeof property === "string") {
124
+ if (this.registerExternalPlugin(property, factory, logger)) {
125
+ const options = hasOwn(configurations, property) ? configurations[property] : true;
126
+ void this.enablePlugin(property, options, context);
127
+ }
128
+ }
129
+ return result;
130
+ },
131
+ deleteProperty: (target, property) => {
132
+ if (this.instance === sdk && typeof property === "string" && hasOwn(this.pluginFactories, property)) {
133
+ logger.error(
134
+ `Plugin "${property}" cannot be unregistered; it will remain registered until the page is reloaded.`
135
+ );
136
+ }
137
+ return Reflect.deleteProperty(target, property);
138
+ }
139
+ });
140
+ }
141
+ /**
142
+ * Registers a plugin factory declared on the global registry.
143
+ *
144
+ * Unlike {@link extend}, conflicts are tolerated so a single malformed or
145
+ * duplicated declaration cannot prevent the plug from initializing. A factory
146
+ * already registered under the same name is silently ignored, allowing the
147
+ * registry to be safely re-scanned across re-plugs.
148
+ *
149
+ * @returns Whether the factory was newly registered.
150
+ */
151
+ registerExternalPlugin(name, factory, logger) {
152
+ if (isReservedPluginName(name)) {
153
+ logger.error(`The plugin name "${name}" is reserved and cannot be used, ignoring it.`);
154
+ return false;
155
+ }
156
+ if (typeof factory !== "function") {
157
+ logger.error(`The plugin "${name}" declared globally is not a valid factory, ignoring it.`);
158
+ return false;
159
+ }
160
+ const registered = hasOwn(this.pluginFactories, name) ? this.pluginFactories[name] : void 0;
161
+ if (registered !== void 0) {
162
+ if (registered !== factory) {
163
+ logger.warn(`Plugin "${name}" is already registered, ignoring global registration.`);
164
+ }
165
+ return false;
166
+ }
167
+ this.pluginFactories[name] = factory;
168
+ return true;
169
+ }
170
+ /**
171
+ * Instantiates and enables a single plugin.
172
+ *
173
+ * @returns A promise that resolves once the plugin is enabled, or nothing if
174
+ * the plugin is synchronous, disabled, or could not be initialized.
175
+ */
176
+ enablePlugin(name, options, context) {
177
+ const { sdk, appId, logger } = context;
178
+ if (isReservedPluginName(name)) {
179
+ logger.error(`The plugin name "${name}" is reserved and cannot be used, ignoring it.`);
180
+ return;
181
+ }
182
+ logger.debug(`Initializing plugin "${name}"...`);
183
+ const factory = hasOwn(this.pluginFactories, name) ? this.pluginFactories[name] : void 0;
184
+ if (factory === void 0) {
185
+ logger.error(`Plugin "${name}" is not registered.`);
186
+ return;
187
+ }
188
+ if (typeof options !== "boolean" && (options === null || typeof options !== "object")) {
189
+ logger.error(
190
+ `Invalid options for plugin "${name}", expected either boolean or object but got ${describe(options)}`
191
+ );
192
+ return;
193
+ }
194
+ if (options === false) {
195
+ logger.warn(`Plugin "${name}" is declared but not enabled`);
196
+ return;
197
+ }
198
+ const args = {
199
+ options: options === true ? {} : options,
200
+ sdk: {
201
+ version: VERSION,
202
+ appId,
203
+ plug: this,
204
+ tracker: sdk.tracker,
205
+ evaluator: sdk.evaluator,
206
+ user: sdk.user,
207
+ session: sdk.session,
208
+ tab: sdk.context.getTab(),
209
+ userTokenStore: {
210
+ getToken: sdk.getToken.bind(sdk),
211
+ setToken: sdk.setToken.bind(sdk)
212
+ },
213
+ previewTokenStore: sdk.previewTokenStore,
214
+ cidAssigner: sdk.cidAssigner,
215
+ eventManager: sdk.eventManager,
216
+ getLogger: (...namespace) => sdk.getLogger(PLUGIN_NAMESPACE, name, ...namespace),
217
+ getTabStorage: (...namespace) => sdk.getTabStorage(PLUGIN_NAMESPACE, name, ...namespace),
218
+ getBrowserStorage: (...namespace) => sdk.getBrowserStorage(PLUGIN_NAMESPACE, name, ...namespace)
219
+ }
220
+ };
221
+ let plugin;
222
+ try {
223
+ plugin = factory(args);
224
+ } catch (error) {
225
+ logger.error(`Failed to initialize plugin "${name}": ${formatCause(error)}`);
226
+ return;
227
+ }
228
+ logger.debug(`Plugin "${name}" initialized`);
229
+ if (typeof plugin !== "object") {
230
+ return;
231
+ }
232
+ this.plugins[name] = plugin;
233
+ const promise = plugin.enable();
234
+ if (!(promise instanceof Promise)) {
235
+ logger.debug(`Plugin "${name}" enabled`);
236
+ return;
237
+ }
238
+ return promise.then(() => logger.debug(`Plugin "${name}" enabled`)).catch((error) => logger.error(`Failed to enable plugin "${name}": ${formatCause(error)}`));
239
+ }
142
240
  get initialized() {
143
241
  return this.instance !== void 0;
144
242
  }
package/plugin.cjs CHANGED
@@ -2,6 +2,10 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
5
9
  var __copyProps = (to, from, except, desc) => {
6
10
  if (from && typeof from === "object" || typeof from === "function") {
7
11
  for (let key of __getOwnPropNames(from))
@@ -12,4 +16,33 @@ var __copyProps = (to, from, except, desc) => {
12
16
  };
13
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
14
18
  var plugin_exports = {};
19
+ __export(plugin_exports, {
20
+ PluginSdk: () => PluginSdk,
21
+ isReservedPluginName: () => isReservedPluginName
22
+ });
15
23
  module.exports = __toCommonJS(plugin_exports);
24
+ const RESERVED_PLUGIN_NAMES = ["__proto__", "constructor", "prototype"];
25
+ function isReservedPluginName(name) {
26
+ return RESERVED_PLUGIN_NAMES.includes(name);
27
+ }
28
+ var PluginSdk;
29
+ ((PluginSdk2) => {
30
+ function register(name, factory) {
31
+ if (isReservedPluginName(name)) {
32
+ throw new Error(`The plugin name "${name}" is reserved and cannot be used.`);
33
+ }
34
+ if (typeof window === "undefined") {
35
+ return;
36
+ }
37
+ if (window.croctPlugins === void 0) {
38
+ window.croctPlugins = {};
39
+ }
40
+ window.croctPlugins[name] = factory;
41
+ }
42
+ PluginSdk2.register = register;
43
+ })(PluginSdk || (PluginSdk = {}));
44
+ // Annotate the CommonJS export names for ESM import in node:
45
+ 0 && (module.exports = {
46
+ PluginSdk,
47
+ isReservedPluginName
48
+ });
package/plugin.d.cts CHANGED
@@ -2,7 +2,7 @@ import '@croct/sdk/token';
2
2
  import '@croct/sdk/facade/evaluatorFacade';
3
3
  import '@croct/sdk/facade/trackerFacade';
4
4
  import './sdk/index.cjs';
5
- export { a as Plugin, b as PluginArguments, c as PluginFactory, d as PluginSdk } from './plug-CPsFBGem.cjs';
5
+ export { b as Plugin, c as PluginArguments, a as PluginFactory, d as PluginSdk, i as isReservedPluginName } from './plug-CIEbOxqZ.cjs';
6
6
  import '@croct/sdk/facade/userFacade';
7
7
  import '@croct/sdk/facade/sessionFacade';
8
8
  import '@croct/sdk/tab';
package/plugin.d.ts CHANGED
@@ -2,7 +2,7 @@ import '@croct/sdk/token';
2
2
  import '@croct/sdk/facade/evaluatorFacade';
3
3
  import '@croct/sdk/facade/trackerFacade';
4
4
  import './sdk/index.js';
5
- export { a as Plugin, b as PluginArguments, c as PluginFactory, d as PluginSdk } from './plug-aoj2L27K.js';
5
+ export { b as Plugin, c as PluginArguments, a as PluginFactory, d as PluginSdk, i as isReservedPluginName } from './plug-DV00uIb1.js';
6
6
  import '@croct/sdk/facade/userFacade';
7
7
  import '@croct/sdk/facade/sessionFacade';
8
8
  import '@croct/sdk/tab';
package/plugin.js CHANGED
@@ -0,0 +1,24 @@
1
+ const RESERVED_PLUGIN_NAMES = ["__proto__", "constructor", "prototype"];
2
+ function isReservedPluginName(name) {
3
+ return RESERVED_PLUGIN_NAMES.includes(name);
4
+ }
5
+ var PluginSdk;
6
+ ((PluginSdk2) => {
7
+ function register(name, factory) {
8
+ if (isReservedPluginName(name)) {
9
+ throw new Error(`The plugin name "${name}" is reserved and cannot be used.`);
10
+ }
11
+ if (typeof window === "undefined") {
12
+ return;
13
+ }
14
+ if (window.croctPlugins === void 0) {
15
+ window.croctPlugins = {};
16
+ }
17
+ window.croctPlugins[name] = factory;
18
+ }
19
+ PluginSdk2.register = register;
20
+ })(PluginSdk || (PluginSdk = {}));
21
+ export {
22
+ PluginSdk,
23
+ isReservedPluginName
24
+ };
@@ -1,4 +1,4 @@
1
- import { a as Plugin, b as PluginArguments } from '../../plug-CPsFBGem.cjs';
1
+ import { b as Plugin, c as PluginArguments } from '../../plug-CIEbOxqZ.cjs';
2
2
  import { TrackerFacade } from '@croct/sdk/facade/trackerFacade';
3
3
  import { Tab } from '@croct/sdk/tab';
4
4
  import '@croct/sdk/facade/sessionFacade';
@@ -1,4 +1,4 @@
1
- import { a as Plugin, b as PluginArguments } from '../../plug-aoj2L27K.js';
1
+ import { b as Plugin, c as PluginArguments } from '../../plug-DV00uIb1.js';
2
2
  import { TrackerFacade } from '@croct/sdk/facade/trackerFacade';
3
3
  import { Tab } from '@croct/sdk/tab';
4
4
  import '@croct/sdk/facade/sessionFacade';
@@ -1,4 +1,4 @@
1
- import { P as Plug, a as Plugin, b as PluginArguments } from '../../plug-CPsFBGem.cjs';
1
+ import { P as Plug, b as Plugin, c as PluginArguments } from '../../plug-CIEbOxqZ.cjs';
2
2
  import '@croct/sdk/facade/sessionFacade';
3
3
  import '@croct/sdk/facade/userFacade';
4
4
  import '@croct/sdk/facade/trackerFacade';
@@ -1,4 +1,4 @@
1
- import { P as Plug, a as Plugin, b as PluginArguments } from '../../plug-aoj2L27K.js';
1
+ import { P as Plug, b as Plugin, c as PluginArguments } from '../../plug-DV00uIb1.js';
2
2
  import '@croct/sdk/facade/sessionFacade';
3
3
  import '@croct/sdk/facade/userFacade';
4
4
  import '@croct/sdk/facade/trackerFacade';
@@ -1,5 +1,5 @@
1
1
  import { Logger } from '@croct/sdk/logging';
2
- import { a as Plugin, c as PluginFactory } from '../../plug-CPsFBGem.cjs';
2
+ import { b as Plugin, a as PluginFactory } from '../../plug-CIEbOxqZ.cjs';
3
3
  import { TokenStore } from '@croct/sdk/token';
4
4
  import '@croct/sdk/facade/sessionFacade';
5
5
  import '@croct/sdk/facade/userFacade';
@@ -1,5 +1,5 @@
1
1
  import { Logger } from '@croct/sdk/logging';
2
- import { a as Plugin, c as PluginFactory } from '../../plug-aoj2L27K.js';
2
+ import { b as Plugin, a as PluginFactory } from '../../plug-DV00uIb1.js';
3
3
  import { TokenStore } from '@croct/sdk/token';
4
4
  import '@croct/sdk/facade/sessionFacade';
5
5
  import '@croct/sdk/facade/userFacade';