@magicfeedback/popup-sdk 1.7.2 → 1.8.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 +8 -5
- package/dist/index.d.mts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +68 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +69 -21
- package/dist/index.mjs.map +1 -1
- package/dist/react-native.d.mts +20 -0
- package/dist/react-native.d.ts +20 -0
- package/dist/react-native.js +66 -18
- package/dist/react-native.js.map +1 -1
- package/dist/react-native.mjs +66 -18
- package/dist/react-native.mjs.map +1 -1
- package/dist/{renderPopup-5QXL6EJW.mjs → renderPopup-HTNVACFV.mjs} +3 -3
- package/dist/renderPopup-HTNVACFV.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/renderPopup-5QXL6EJW.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Package name: `@magicfeedback/popup-sdk`
|
|
|
9
9
|
- Loads popup definitions from the Deepdots API
|
|
10
10
|
- Triggers popups by time on page, scroll depth, click, route exit, or host-driven events
|
|
11
11
|
- Emits lifecycle events so the host app can track popup activity
|
|
12
|
-
- Supports route and language targeting through `segments.path` / `segments.lang`
|
|
12
|
+
- Supports route and language targeting through `segments.path` / `segments.excludedPaths` / `segments.lang`
|
|
13
13
|
- Collects analytics (navigation, engagement, messaging, crashes) and sends them to Deepdots
|
|
14
14
|
- Falls back to a no-op renderer outside the browser
|
|
15
15
|
|
|
@@ -345,8 +345,8 @@ Popup definitions use these trigger types:
|
|
|
345
345
|
## Popup Definition Shape
|
|
346
346
|
|
|
347
347
|
The API returns popup definitions with this structure. `triggers` is an array, `time_on_page`
|
|
348
|
-
values are in seconds, and `segments.path`
|
|
349
|
-
or hash routes such as `/#/home`:
|
|
348
|
+
values are in seconds, and `segments.path` / `segments.excludedPaths` accept full URLs, path
|
|
349
|
+
fragments such as `/pricing`, or hash routes such as `/#/home`:
|
|
350
350
|
|
|
351
351
|
```ts
|
|
352
352
|
interface PopupDefinition {
|
|
@@ -395,7 +395,8 @@ interface PopupDefinition {
|
|
|
395
395
|
};
|
|
396
396
|
};
|
|
397
397
|
segments?: {
|
|
398
|
-
path?: string[];
|
|
398
|
+
path?: string[]; // routes where it can be shown (empty/absent = all routes)
|
|
399
|
+
excludedPaths?: string[]; // routes where it must NOT be shown; wins over `path`
|
|
399
400
|
lang?: string[];
|
|
400
401
|
[key: string]: unknown;
|
|
401
402
|
};
|
|
@@ -431,7 +432,9 @@ interface DeepdotsEvent {
|
|
|
431
432
|
|
|
432
433
|
## Important Behavior Notes
|
|
433
434
|
|
|
434
|
-
- `segments.path` and `segments.lang` are the segments currently evaluated by the SDK runtime. If no language can be resolved, popups with `segments.lang` are shown rather than filtered out.
|
|
435
|
+
- `segments.path`, `segments.excludedPaths`, and `segments.lang` are the segments currently evaluated by the SDK runtime. If no language can be resolved, popups with `segments.lang` are shown rather than filtered out.
|
|
436
|
+
- `segments.excludedPaths` wins over `segments.path`, and applies on its own when `path` is absent. So `{ "path": ["/"], "excludedPaths": ["/cart"] }` means "everywhere except the cart", and `{ "excludedPaths": ["/cart"] }` means the same thing. Both lists use the same matching rules, which includes the substring match for entries starting with `/` — an entry such as `/cart` also excludes `/cart-recovery`, and `/` on its own would exclude every route.
|
|
437
|
+
- For a deferred `exit` popup, `segments.excludedPaths` is checked twice: on the route the popup was queued from, and again on the destination route where it would be painted. `segments.path` is only checked on the first one, because the destination route is different by definition.
|
|
435
438
|
- `exit` triggers work across anchor navigation, hash navigation, `history.pushState()`, and `history.replaceState()`.
|
|
436
439
|
- Pending `exit` popups are stored in `sessionStorage` until they are shown or discarded.
|
|
437
440
|
- The default browser renderer uses Deepdots forms and applies button labels from `actions.accept`, `actions.start`, `actions.back`, and `actions.complete`.
|
package/dist/index.d.mts
CHANGED
|
@@ -391,6 +391,15 @@ interface PopupStyle {
|
|
|
391
391
|
interface PopupSegments {
|
|
392
392
|
lang?: string[];
|
|
393
393
|
path?: string[];
|
|
394
|
+
/**
|
|
395
|
+
* App routes where it must NOT be shown. Wins over `path`: a route listed here
|
|
396
|
+
* blocks the popup even if `path` matches it (so `path: ['/']` +
|
|
397
|
+
* `excludedPaths: ['/cart']` = everywhere but the cart).
|
|
398
|
+
* Same matching rules as `path`: an absolute URL matches the full href, an entry
|
|
399
|
+
* starting with `/` matches any href containing it (this is what covers hash
|
|
400
|
+
* routes like `/#/cart`), anything else matches the pathname exactly.
|
|
401
|
+
*/
|
|
402
|
+
excludedPaths?: string[];
|
|
394
403
|
[key: string]: unknown;
|
|
395
404
|
}
|
|
396
405
|
/** Full popup definition */
|
|
@@ -823,7 +832,27 @@ declare class DeepdotsPopups {
|
|
|
823
832
|
private showDefinition;
|
|
824
833
|
private shouldShow;
|
|
825
834
|
private matchesSegmentsLang;
|
|
835
|
+
/** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
|
|
826
836
|
private matchesSegmentsPath;
|
|
837
|
+
/**
|
|
838
|
+
* `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
|
|
839
|
+
* `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
|
|
840
|
+
* carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
|
|
841
|
+
*/
|
|
842
|
+
private isPathExcluded;
|
|
843
|
+
/**
|
|
844
|
+
* Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
|
|
845
|
+
* actual. Mismas reglas para incluir y para excluir:
|
|
846
|
+
* - URL absoluta → href completo exacto
|
|
847
|
+
* - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
|
|
848
|
+
* - resto → pathname exacto
|
|
849
|
+
* Ignora entradas que no sean string: el backend puede colar un `null` en la lista
|
|
850
|
+
* y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
|
|
851
|
+
* `segments.lang`).
|
|
852
|
+
*/
|
|
853
|
+
private matchesPathCandidate;
|
|
854
|
+
/** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
|
|
855
|
+
private resolveCurrentLocation;
|
|
827
856
|
private evaluateCooldown;
|
|
828
857
|
private evaluateLegacyCondition;
|
|
829
858
|
private hasCooldownElapsed;
|
package/dist/index.d.ts
CHANGED
|
@@ -391,6 +391,15 @@ interface PopupStyle {
|
|
|
391
391
|
interface PopupSegments {
|
|
392
392
|
lang?: string[];
|
|
393
393
|
path?: string[];
|
|
394
|
+
/**
|
|
395
|
+
* App routes where it must NOT be shown. Wins over `path`: a route listed here
|
|
396
|
+
* blocks the popup even if `path` matches it (so `path: ['/']` +
|
|
397
|
+
* `excludedPaths: ['/cart']` = everywhere but the cart).
|
|
398
|
+
* Same matching rules as `path`: an absolute URL matches the full href, an entry
|
|
399
|
+
* starting with `/` matches any href containing it (this is what covers hash
|
|
400
|
+
* routes like `/#/cart`), anything else matches the pathname exactly.
|
|
401
|
+
*/
|
|
402
|
+
excludedPaths?: string[];
|
|
394
403
|
[key: string]: unknown;
|
|
395
404
|
}
|
|
396
405
|
/** Full popup definition */
|
|
@@ -823,7 +832,27 @@ declare class DeepdotsPopups {
|
|
|
823
832
|
private showDefinition;
|
|
824
833
|
private shouldShow;
|
|
825
834
|
private matchesSegmentsLang;
|
|
835
|
+
/** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
|
|
826
836
|
private matchesSegmentsPath;
|
|
837
|
+
/**
|
|
838
|
+
* `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
|
|
839
|
+
* `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
|
|
840
|
+
* carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
|
|
841
|
+
*/
|
|
842
|
+
private isPathExcluded;
|
|
843
|
+
/**
|
|
844
|
+
* Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
|
|
845
|
+
* actual. Mismas reglas para incluir y para excluir:
|
|
846
|
+
* - URL absoluta → href completo exacto
|
|
847
|
+
* - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
|
|
848
|
+
* - resto → pathname exacto
|
|
849
|
+
* Ignora entradas que no sean string: el backend puede colar un `null` en la lista
|
|
850
|
+
* y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
|
|
851
|
+
* `segments.lang`).
|
|
852
|
+
*/
|
|
853
|
+
private matchesPathCandidate;
|
|
854
|
+
/** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
|
|
855
|
+
private resolveCurrentLocation;
|
|
827
856
|
private evaluateCooldown;
|
|
828
857
|
private evaluateLegacyCondition;
|
|
829
858
|
private hasCooldownElapsed;
|
package/dist/index.js
CHANGED
|
@@ -2431,9 +2431,9 @@ var init_logo = __esm({
|
|
|
2431
2431
|
}
|
|
2432
2432
|
});
|
|
2433
2433
|
|
|
2434
|
-
//
|
|
2434
|
+
// node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js
|
|
2435
2435
|
var require_magicfeedback_sdk_browser = __commonJS({
|
|
2436
|
-
"
|
|
2436
|
+
"node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js"(exports, module2) {
|
|
2437
2437
|
"use strict";
|
|
2438
2438
|
!(function(e, t) {
|
|
2439
2439
|
"object" == typeof exports && "object" == typeof module2 ? module2.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.magicfeedback = t() : e.magicfeedback = t();
|
|
@@ -7948,6 +7948,9 @@ var DeepdotsPopups = class {
|
|
|
7948
7948
|
this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
|
|
7949
7949
|
}
|
|
7950
7950
|
shouldShow(def, pathUrl, skipPathCheck = false) {
|
|
7951
|
+
if (this.isPathExcluded(def, pathUrl)) {
|
|
7952
|
+
return false;
|
|
7953
|
+
}
|
|
7951
7954
|
if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {
|
|
7952
7955
|
return false;
|
|
7953
7956
|
}
|
|
@@ -7978,40 +7981,85 @@ var DeepdotsPopups = class {
|
|
|
7978
7981
|
}
|
|
7979
7982
|
return matches;
|
|
7980
7983
|
}
|
|
7984
|
+
/** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
|
|
7981
7985
|
matchesSegmentsPath(def, pathUrl) {
|
|
7982
7986
|
const paths = def.segments?.path;
|
|
7983
|
-
if (!paths || paths.length === 0) return true;
|
|
7984
|
-
|
|
7987
|
+
if (!Array.isArray(paths) || paths.length === 0) return true;
|
|
7988
|
+
const location = this.resolveCurrentLocation(pathUrl);
|
|
7989
|
+
if (!location) {
|
|
7985
7990
|
this.debug("No window.location available for path comparison", { popupId: def.id, paths });
|
|
7986
7991
|
return true;
|
|
7987
7992
|
}
|
|
7988
|
-
const normalizedHref = this.normalizeUrl(pathUrl || window.location.href || "");
|
|
7989
|
-
const currentUrl = this.safeParseUrl(normalizedHref);
|
|
7990
|
-
const normalizedPath = this.normalizeUrl(currentUrl?.pathname || window.location.pathname || "");
|
|
7991
7993
|
const matches = paths.some((rawCandidate) => {
|
|
7992
|
-
const
|
|
7993
|
-
let match = false;
|
|
7994
|
-
if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
|
|
7995
|
-
match = normalizedHref === candidate;
|
|
7996
|
-
} else if (candidate.startsWith("/")) {
|
|
7997
|
-
match = normalizedHref.includes(candidate);
|
|
7998
|
-
} else {
|
|
7999
|
-
match = normalizedPath === candidate;
|
|
8000
|
-
}
|
|
7994
|
+
const match = this.matchesPathCandidate(rawCandidate, location);
|
|
8001
7995
|
this.debug("Path comparison", {
|
|
8002
7996
|
popupId: def.id,
|
|
8003
|
-
candidate,
|
|
8004
|
-
currentPath:
|
|
8005
|
-
currentHref:
|
|
7997
|
+
candidate: rawCandidate,
|
|
7998
|
+
currentPath: location.path,
|
|
7999
|
+
currentHref: location.href,
|
|
8006
8000
|
match
|
|
8007
8001
|
});
|
|
8008
8002
|
return match;
|
|
8009
8003
|
});
|
|
8010
8004
|
if (!matches) {
|
|
8011
|
-
this.debug("No path match for popup", { popupId: def.id, paths, currentPath:
|
|
8005
|
+
this.debug("No path match for popup", { popupId: def.id, paths, currentPath: location.path });
|
|
8012
8006
|
}
|
|
8013
8007
|
return matches;
|
|
8014
8008
|
}
|
|
8009
|
+
/**
|
|
8010
|
+
* `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
|
|
8011
|
+
* `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
|
|
8012
|
+
* carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
|
|
8013
|
+
*/
|
|
8014
|
+
isPathExcluded(def, pathUrl) {
|
|
8015
|
+
const excluded = def.segments?.excludedPaths;
|
|
8016
|
+
if (!Array.isArray(excluded) || excluded.length === 0) return false;
|
|
8017
|
+
const location = this.resolveCurrentLocation(pathUrl);
|
|
8018
|
+
if (!location) {
|
|
8019
|
+
this.debug("No window.location available for excluded path comparison", { popupId: def.id, excluded });
|
|
8020
|
+
return false;
|
|
8021
|
+
}
|
|
8022
|
+
const hit = excluded.find((rawCandidate) => this.matchesPathCandidate(rawCandidate, location));
|
|
8023
|
+
if (hit !== void 0) {
|
|
8024
|
+
this.debug("Popup excluded by path", {
|
|
8025
|
+
popupId: def.id,
|
|
8026
|
+
candidate: hit,
|
|
8027
|
+
currentPath: location.path,
|
|
8028
|
+
currentHref: location.href
|
|
8029
|
+
});
|
|
8030
|
+
return true;
|
|
8031
|
+
}
|
|
8032
|
+
return false;
|
|
8033
|
+
}
|
|
8034
|
+
/**
|
|
8035
|
+
* Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
|
|
8036
|
+
* actual. Mismas reglas para incluir y para excluir:
|
|
8037
|
+
* - URL absoluta → href completo exacto
|
|
8038
|
+
* - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
|
|
8039
|
+
* - resto → pathname exacto
|
|
8040
|
+
* Ignora entradas que no sean string: el backend puede colar un `null` en la lista
|
|
8041
|
+
* y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
|
|
8042
|
+
* `segments.lang`).
|
|
8043
|
+
*/
|
|
8044
|
+
matchesPathCandidate(rawCandidate, location) {
|
|
8045
|
+
if (typeof rawCandidate !== "string") return false;
|
|
8046
|
+
const candidate = this.normalizeUrl(rawCandidate);
|
|
8047
|
+
if (!candidate) return false;
|
|
8048
|
+
if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
|
|
8049
|
+
return location.href === candidate;
|
|
8050
|
+
}
|
|
8051
|
+
if (candidate.startsWith("/")) {
|
|
8052
|
+
return location.href.includes(candidate);
|
|
8053
|
+
}
|
|
8054
|
+
return location.path === candidate;
|
|
8055
|
+
}
|
|
8056
|
+
/** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
|
|
8057
|
+
resolveCurrentLocation(pathUrl) {
|
|
8058
|
+
if (typeof window === "undefined" || !window.location) return null;
|
|
8059
|
+
const href = this.normalizeUrl(pathUrl || window.location.href || "");
|
|
8060
|
+
const parsed = this.safeParseUrl(href);
|
|
8061
|
+
return { href, path: this.normalizeUrl(parsed?.pathname || window.location.pathname || "") };
|
|
8062
|
+
}
|
|
8015
8063
|
evaluateCooldown(def, condition) {
|
|
8016
8064
|
switch (condition.answered) {
|
|
8017
8065
|
case "SHOWED":
|