@enquio/popup-core 1.0.2 → 1.2.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 ADDED
@@ -0,0 +1,55 @@
1
+ # @enquio/popup-core
2
+
3
+ See the [installation prerequisites](../../docs/installation.md), [support matrix](../../docs/support-matrix.md) and [troubleshooting](../../docs/troubleshooting.md). Registry commands assume a matching published release; local verification uses tarballs and does not establish npm/CDN availability.
4
+
5
+ Shared browser loader used by the React, Vue and Angular popup wrappers.
6
+
7
+ ```sh
8
+ npm install @enquio/popup-core
9
+ ```
10
+
11
+ The package provides native ESM (`dist/index.mjs`) and CommonJS (`dist/index.js`) entrypoints with matching TypeScript declarations selected through conditional exports. Import from `@enquio/popup-core`; both formats share the same per-window loader state.
12
+
13
+ <!-- example:core -->
14
+
15
+ ```ts
16
+ import { loadEnquioScript } from '@enquio/popup-core';
17
+
18
+ try {
19
+ await loadEnquioScript();
20
+ // Renderer initialization has completed and enquio-popup is registered.
21
+ } catch {
22
+ // Enquio already emitted a console diagnostic. Leave the host website usable.
23
+ }
24
+ ```
25
+
26
+ ## Readiness
27
+
28
+ The returned `Promise<void>` represents renderer readiness, not just the `popup.js` load event. It does not wait for a project's rules, form data or display trigger. The core does not create the popup element itself; framework wrappers own their element and the HTML snippet supports its existing `data-enquio-popup` auto-mount behavior.
29
+
30
+ Concurrent calls share an attempt across package copies in the same window. No browser APIs are accessed on SSR: the resolved server-side result is a no-op, not a browser readiness guarantee.
31
+
32
+ ## Quiet failures and explicit retry
33
+
34
+ Each caller waits up to 30 seconds. The core emits at most one `Enquio:` console warning per shared attempt and rejects the promise. Supplied wrappers catch it without error UI. Custom consumers must also catch rejection. Native browser network/script diagnostics are not suppressed.
35
+
36
+ A timeout does not cancel the underlying script or Angular startup. Late completion is allowed; another call joins the pending operation and gets a fresh observation window without downloading another copy. The bootstrap dispatches `enquio:ready` once on eventual success.
37
+
38
+ After a confirmed network failure, a subsequent explicit `loadEnquioScript()` call retries the failed resource. There is no automatic retry loop. Successful/pending modules are reused. Execution or Angular initialization errors are terminal until reload; the loader does not start another Angular runtime.
39
+
40
+ An existing script with no observed load/error event and no initialized controller cannot safely be classified as failed. It times out rather than being removed speculatively. If it is known to have failed before the core attached, explicitly replace it or reload the page.
41
+
42
+ ## CSP and consent
43
+
44
+ `loadEnquioScript({ nonce: responseNonce })` supplies the host's CSP nonce before the first load. Without options, the loader discovers the first `script[nonce]`. No-argument calls remain supported. Nonce is propagated to the popup script, renderer modules and Angular styles, including a replacement script after a transport failure.
45
+
46
+ Importing the package alone makes no requests. Call the loader or mount a wrapper only after the host's consent decision. The first connected popup host owns the session; later hosts stay inactive and conflicting projects are reported. Removing the owner does not automatically activate an ignored host. See [the GC-007 contract](../../../EnquioClient/docs/integrations/embed-compatibility.md) for the tested CSP example, revocation limits, mixed integrations and safe layout placement.
47
+
48
+ ## Tests and coordinated release
49
+
50
+ - `npm run test:loader` from the workspace root checks SSR core loading and the Angular wrapper lifecycle with decorator stubs. It is not a full Angular SSR/hydration test.
51
+ - `npm run build` builds the workspace packages.
52
+ - `npm run test:react-package` verifies packed core and React artifacts in isolated ESM/CommonJS and TypeScript consumers, plus three-engine lifecycle tests across React 17–19.
53
+ - The companion EnquioClient checkout owns the cross-repository browser suite: `npm run test:bootstrap`. It builds the production renderer and tests Chromium, Firefox and WebKit using intercepted API responses. Set `ENQUIO_WRAPPERS_PATH` there if this checkout is not its sibling.
54
+
55
+ The private bootstrap window contract is mirrored between this loader and `EnquioClient/common/runtime/bootstrap.ts`; it is not public API. Release the public popup and renderer files together, then the coordinated wrappers. No version detection or legacy fallback is implemented. See GC-001 and EnquioClient's popup-embed documentation for detailed behavior and deployment boundaries.
@@ -0,0 +1,29 @@
1
+ interface LoadEnquioOptions {
2
+ nonce?: string;
3
+ }
4
+ interface LoadContext {
5
+ reported: boolean;
6
+ }
7
+ interface PopupBootstrap {
8
+ context: LoadContext;
9
+ current?: Promise<void>;
10
+ load(context?: LoadContext): Promise<void>;
11
+ }
12
+ interface ScriptLoad {
13
+ context: LoadContext;
14
+ task?: Promise<void>;
15
+ wait?: Promise<void>;
16
+ failedScript?: HTMLScriptElement;
17
+ }
18
+ declare global {
19
+ interface Window {
20
+ __ENQUIO_NONCE__?: string;
21
+ __ENQUIO_POPUP_BOOTSTRAP__?: PopupBootstrap;
22
+ __ENQUIO_LOAD_CONTEXT__?: LoadContext;
23
+ __ENQUIO_SCRIPT_LOAD__?: ScriptLoad;
24
+ __ENQUIO_RENDERER_BOOTSTRAP__?: Promise<void>;
25
+ }
26
+ }
27
+ declare function loadEnquioScript(options?: LoadEnquioOptions): Promise<void>;
28
+
29
+ export { type LoadEnquioOptions, loadEnquioScript };
package/dist/index.d.ts CHANGED
@@ -1 +1,29 @@
1
- export * from './loader';
1
+ interface LoadEnquioOptions {
2
+ nonce?: string;
3
+ }
4
+ interface LoadContext {
5
+ reported: boolean;
6
+ }
7
+ interface PopupBootstrap {
8
+ context: LoadContext;
9
+ current?: Promise<void>;
10
+ load(context?: LoadContext): Promise<void>;
11
+ }
12
+ interface ScriptLoad {
13
+ context: LoadContext;
14
+ task?: Promise<void>;
15
+ wait?: Promise<void>;
16
+ failedScript?: HTMLScriptElement;
17
+ }
18
+ declare global {
19
+ interface Window {
20
+ __ENQUIO_NONCE__?: string;
21
+ __ENQUIO_POPUP_BOOTSTRAP__?: PopupBootstrap;
22
+ __ENQUIO_LOAD_CONTEXT__?: LoadContext;
23
+ __ENQUIO_SCRIPT_LOAD__?: ScriptLoad;
24
+ __ENQUIO_RENDERER_BOOTSTRAP__?: Promise<void>;
25
+ }
26
+ }
27
+ declare function loadEnquioScript(options?: LoadEnquioOptions): Promise<void>;
28
+
29
+ export { type LoadEnquioOptions, loadEnquioScript };
package/dist/index.js CHANGED
@@ -1 +1,144 @@
1
- export * from './loader';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ loadEnquioScript: () => loadEnquioScript
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/loader.ts
28
+ var SCRIPT_URL = "https://public.newapp.enquio.eu/popup.js";
29
+ var SCRIPT_ID = "enquio-popup-script";
30
+ var LOAD_TIMEOUT = 3e4;
31
+ var ScriptLoadError = class extends Error {
32
+ constructor(message, retryable = false) {
33
+ super(message);
34
+ this.retryable = retryable;
35
+ }
36
+ };
37
+ function report(state, error) {
38
+ if (state.context.reported) return;
39
+ state.context.reported = true;
40
+ console.warn("Enquio:", error instanceof Error ? error.message : String(error));
41
+ }
42
+ function loadPopupScript(state) {
43
+ if (window.__ENQUIO_POPUP_BOOTSTRAP__) return Promise.resolve();
44
+ return new Promise((resolve, reject) => {
45
+ var _a;
46
+ const existing = document.getElementById(SCRIPT_ID);
47
+ let script;
48
+ let insert = false;
49
+ if (existing && !(existing instanceof HTMLScriptElement)) {
50
+ reject(new Error("[script] enquio-popup-script is not a script element."));
51
+ return;
52
+ }
53
+ if (existing && existing !== state.failedScript) {
54
+ script = existing;
55
+ } else {
56
+ script = document.createElement("script");
57
+ if (existing) {
58
+ for (const attribute of Array.from(existing.attributes)) script.setAttribute(attribute.name, attribute.value);
59
+ }
60
+ script.nonce = (existing == null ? void 0 : existing.nonce) || window.__ENQUIO_NONCE__ || "";
61
+ script.src = SCRIPT_URL;
62
+ script.id = SCRIPT_ID;
63
+ script.async = true;
64
+ existing == null ? void 0 : existing.remove();
65
+ state.failedScript = void 0;
66
+ insert = true;
67
+ }
68
+ const cleanup = () => {
69
+ script.removeEventListener("load", loaded);
70
+ script.removeEventListener("error", failed);
71
+ window.removeEventListener("enquio:ready", ready);
72
+ };
73
+ const loaded = () => {
74
+ cleanup();
75
+ resolve();
76
+ };
77
+ const failed = () => {
78
+ cleanup();
79
+ state.failedScript = script;
80
+ reject(new ScriptLoadError("[script] Could not load popup.js.", true));
81
+ };
82
+ const ready = () => {
83
+ if (window.__ENQUIO_POPUP_BOOTSTRAP__) loaded();
84
+ };
85
+ script.addEventListener("load", loaded);
86
+ script.addEventListener("error", failed);
87
+ window.addEventListener("enquio:ready", ready);
88
+ window.__ENQUIO_LOAD_CONTEXT__ = state.context;
89
+ if (insert) ((_a = document.head) != null ? _a : document.documentElement).appendChild(script);
90
+ });
91
+ }
92
+ function loadEnquioScript(options = {}) {
93
+ var _a, _b, _c, _d;
94
+ if (typeof window === "undefined") return Promise.resolve();
95
+ (_c = window.__ENQUIO_NONCE__) != null ? _c : window.__ENQUIO_NONCE__ = (_b = options.nonce) != null ? _b : (_a = document.querySelector("script[nonce]")) == null ? void 0 : _a.nonce;
96
+ const state = (_d = window.__ENQUIO_SCRIPT_LOAD__) != null ? _d : window.__ENQUIO_SCRIPT_LOAD__ = { context: { reported: false } };
97
+ if (!state.task) {
98
+ state.context = { reported: false };
99
+ state.task = Promise.resolve().then(async () => {
100
+ const existingController = window.__ENQUIO_POPUP_BOOTSTRAP__;
101
+ if (!existingController && customElements.get("enquio-popup")) {
102
+ await window.__ENQUIO_RENDERER_BOOTSTRAP__;
103
+ return;
104
+ }
105
+ await loadPopupScript(state);
106
+ const controller = window.__ENQUIO_POPUP_BOOTSTRAP__;
107
+ if (!controller) throw new Error("[script] popup.js did not initialize its bootstrap controller.");
108
+ const task = existingController ? controller.load(state.context) : controller.current;
109
+ state.context = controller.context;
110
+ if (!task) throw new Error("[script] popup.js did not start its bootstrap.");
111
+ await task;
112
+ }).catch((error) => {
113
+ if (error instanceof Error && "retryable" in error && error.retryable === true) state.task = void 0;
114
+ throw error;
115
+ });
116
+ }
117
+ if (!state.wait) {
118
+ state.wait = new Promise((resolve, reject) => {
119
+ const timer = window.setTimeout(() => {
120
+ const error = new Error("[timeout] Renderer was not ready within 30 seconds. Loading may still complete.");
121
+ report(state, error);
122
+ reject(error);
123
+ }, LOAD_TIMEOUT);
124
+ state.task.then(
125
+ () => {
126
+ window.clearTimeout(timer);
127
+ resolve();
128
+ },
129
+ (error) => {
130
+ window.clearTimeout(timer);
131
+ report(state, error);
132
+ reject(error);
133
+ }
134
+ );
135
+ }).finally(() => {
136
+ state.wait = void 0;
137
+ });
138
+ }
139
+ return state.wait;
140
+ }
141
+ // Annotate the CommonJS export names for ESM import in node:
142
+ 0 && (module.exports = {
143
+ loadEnquioScript
144
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,117 @@
1
+ // src/loader.ts
2
+ var SCRIPT_URL = "https://public.newapp.enquio.eu/popup.js";
3
+ var SCRIPT_ID = "enquio-popup-script";
4
+ var LOAD_TIMEOUT = 3e4;
5
+ var ScriptLoadError = class extends Error {
6
+ constructor(message, retryable = false) {
7
+ super(message);
8
+ this.retryable = retryable;
9
+ }
10
+ };
11
+ function report(state, error) {
12
+ if (state.context.reported) return;
13
+ state.context.reported = true;
14
+ console.warn("Enquio:", error instanceof Error ? error.message : String(error));
15
+ }
16
+ function loadPopupScript(state) {
17
+ if (window.__ENQUIO_POPUP_BOOTSTRAP__) return Promise.resolve();
18
+ return new Promise((resolve, reject) => {
19
+ var _a;
20
+ const existing = document.getElementById(SCRIPT_ID);
21
+ let script;
22
+ let insert = false;
23
+ if (existing && !(existing instanceof HTMLScriptElement)) {
24
+ reject(new Error("[script] enquio-popup-script is not a script element."));
25
+ return;
26
+ }
27
+ if (existing && existing !== state.failedScript) {
28
+ script = existing;
29
+ } else {
30
+ script = document.createElement("script");
31
+ if (existing) {
32
+ for (const attribute of Array.from(existing.attributes)) script.setAttribute(attribute.name, attribute.value);
33
+ }
34
+ script.nonce = (existing == null ? void 0 : existing.nonce) || window.__ENQUIO_NONCE__ || "";
35
+ script.src = SCRIPT_URL;
36
+ script.id = SCRIPT_ID;
37
+ script.async = true;
38
+ existing == null ? void 0 : existing.remove();
39
+ state.failedScript = void 0;
40
+ insert = true;
41
+ }
42
+ const cleanup = () => {
43
+ script.removeEventListener("load", loaded);
44
+ script.removeEventListener("error", failed);
45
+ window.removeEventListener("enquio:ready", ready);
46
+ };
47
+ const loaded = () => {
48
+ cleanup();
49
+ resolve();
50
+ };
51
+ const failed = () => {
52
+ cleanup();
53
+ state.failedScript = script;
54
+ reject(new ScriptLoadError("[script] Could not load popup.js.", true));
55
+ };
56
+ const ready = () => {
57
+ if (window.__ENQUIO_POPUP_BOOTSTRAP__) loaded();
58
+ };
59
+ script.addEventListener("load", loaded);
60
+ script.addEventListener("error", failed);
61
+ window.addEventListener("enquio:ready", ready);
62
+ window.__ENQUIO_LOAD_CONTEXT__ = state.context;
63
+ if (insert) ((_a = document.head) != null ? _a : document.documentElement).appendChild(script);
64
+ });
65
+ }
66
+ function loadEnquioScript(options = {}) {
67
+ var _a, _b, _c, _d;
68
+ if (typeof window === "undefined") return Promise.resolve();
69
+ (_c = window.__ENQUIO_NONCE__) != null ? _c : window.__ENQUIO_NONCE__ = (_b = options.nonce) != null ? _b : (_a = document.querySelector("script[nonce]")) == null ? void 0 : _a.nonce;
70
+ const state = (_d = window.__ENQUIO_SCRIPT_LOAD__) != null ? _d : window.__ENQUIO_SCRIPT_LOAD__ = { context: { reported: false } };
71
+ if (!state.task) {
72
+ state.context = { reported: false };
73
+ state.task = Promise.resolve().then(async () => {
74
+ const existingController = window.__ENQUIO_POPUP_BOOTSTRAP__;
75
+ if (!existingController && customElements.get("enquio-popup")) {
76
+ await window.__ENQUIO_RENDERER_BOOTSTRAP__;
77
+ return;
78
+ }
79
+ await loadPopupScript(state);
80
+ const controller = window.__ENQUIO_POPUP_BOOTSTRAP__;
81
+ if (!controller) throw new Error("[script] popup.js did not initialize its bootstrap controller.");
82
+ const task = existingController ? controller.load(state.context) : controller.current;
83
+ state.context = controller.context;
84
+ if (!task) throw new Error("[script] popup.js did not start its bootstrap.");
85
+ await task;
86
+ }).catch((error) => {
87
+ if (error instanceof Error && "retryable" in error && error.retryable === true) state.task = void 0;
88
+ throw error;
89
+ });
90
+ }
91
+ if (!state.wait) {
92
+ state.wait = new Promise((resolve, reject) => {
93
+ const timer = window.setTimeout(() => {
94
+ const error = new Error("[timeout] Renderer was not ready within 30 seconds. Loading may still complete.");
95
+ report(state, error);
96
+ reject(error);
97
+ }, LOAD_TIMEOUT);
98
+ state.task.then(
99
+ () => {
100
+ window.clearTimeout(timer);
101
+ resolve();
102
+ },
103
+ (error) => {
104
+ window.clearTimeout(timer);
105
+ report(state, error);
106
+ reject(error);
107
+ }
108
+ );
109
+ }).finally(() => {
110
+ state.wait = void 0;
111
+ });
112
+ }
113
+ return state.wait;
114
+ }
115
+ export {
116
+ loadEnquioScript
117
+ };
package/package.json CHANGED
@@ -1,12 +1,35 @@
1
1
  {
2
2
  "name": "@enquio/popup-core",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/skutam/EnquioWrappers.git",
7
+ "directory": "packages/core"
8
+ },
4
9
  "main": "dist/index.js",
5
10
  "types": "dist/index.d.ts",
6
11
  "files": [
7
12
  "dist"
8
13
  ],
9
14
  "scripts": {
10
- "build": "tsc"
15
+ "build": "tsup",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "module": "dist/index.mjs",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.mts",
23
+ "default": "./dist/index.mjs"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ }
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.5.1",
33
+ "typescript": "^5.3.0"
11
34
  }
12
35
  }
package/dist/loader.d.ts DELETED
@@ -1,6 +0,0 @@
1
- declare global {
2
- interface Window {
3
- __ENQUIO_SCRIPT_PROMISE__?: Promise<void>;
4
- }
5
- }
6
- export declare function loadEnquioScript(): Promise<void>;
package/dist/loader.js DELETED
@@ -1,23 +0,0 @@
1
- const SCRIPT_URL = 'https://public.newapp.enquio.eu/popup.js';
2
- export function loadEnquioScript() {
3
- if (typeof window === 'undefined') {
4
- return Promise.resolve();
5
- }
6
- if (!window.__ENQUIO_SCRIPT_PROMISE__) {
7
- window.__ENQUIO_SCRIPT_PROMISE__ = new Promise((resolve, reject) => {
8
- const existing = document.getElementById('enquio-popup-script');
9
- if (existing) {
10
- resolve();
11
- return;
12
- }
13
- const script = document.createElement('script');
14
- script.src = SCRIPT_URL;
15
- script.async = true;
16
- script.id = 'enquio-popup-script';
17
- script.onload = () => resolve();
18
- script.onerror = () => reject(new Error('Failed to load Enquio popup'));
19
- document.body.appendChild(script);
20
- });
21
- }
22
- return window.__ENQUIO_SCRIPT_PROMISE__;
23
- }