@favish/staffbase-utils 0.3.0 → 0.4.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/dist/links.cjs.js +2 -0
- package/dist/links.cjs.js.map +1 -0
- package/dist/links.es.mjs +98 -0
- package/dist/links.es.mjs.map +1 -0
- package/dist/src/links/getInAppOpenLinkTarget.d.ts +11 -0
- package/dist/src/links/getInAppOpenLinkTarget.d.ts.map +1 -0
- package/dist/src/links/index.d.ts +7 -0
- package/dist/src/links/index.d.ts.map +1 -0
- package/dist/src/links/isAllowedIframeSrc.d.ts +9 -0
- package/dist/src/links/isAllowedIframeSrc.d.ts.map +1 -0
- package/dist/src/links/isPromiseLike.d.ts +8 -0
- package/dist/src/links/isPromiseLike.d.ts.map +1 -0
- package/dist/src/links/isSafeNavigationHref.d.ts +10 -0
- package/dist/src/links/isSafeNavigationHref.d.ts.map +1 -0
- package/dist/src/links/normalizeInAppLinks.d.ts +14 -0
- package/dist/src/links/normalizeInAppLinks.d.ts.map +1 -0
- package/dist/src/links/openStaffbaseAware.d.ts +9 -0
- package/dist/src/links/openStaffbaseAware.d.ts.map +1 -0
- package/dist/src/links/stripStaffbaseLinkPrefix.d.ts +8 -0
- package/dist/src/links/stripStaffbaseLinkPrefix.d.ts.map +1 -0
- package/dist/src/links/tryOpenWithStaffbase.d.ts +10 -0
- package/dist/src/links/tryOpenWithStaffbase.d.ts.map +1 -0
- package/dist/src/types/links/TryOpenResult.d.ts +10 -0
- package/dist/src/types/links/TryOpenResult.d.ts.map +1 -0
- package/dist/src/types/links/WindowWithStaffbase.d.ts +14 -0
- package/dist/src/types/links/WindowWithStaffbase.d.ts.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=e=>e.startsWith(`/deeplink/`)||e.startsWith(`/openlink/`)?`/${e.slice(10)}`:e,t=(t,n)=>{let r=t?.trim();if(!r||r.startsWith(`#`))return null;let i=r.toLowerCase();if(i.startsWith(`mailto:`)||i.startsWith(`tel:`)||i.startsWith(`sms:`)||i.startsWith(`javascript:`)||i.startsWith(`data:`))return null;try{let t=new URL(n),i=new URL(r,t.toString());if(i.origin===t.origin){let n=e(i.pathname);return new URL(`${n}${i.search}${i.hash}`,t.origin).toString()}return i.toString()}catch{return null}},n=[`youtube.com`,`youtube-nocookie.com`,`youtu.be`,`vimeo.com`,`player.vimeo.com`,`staffbase.com`,`staffbase.rocks`],r=e=>{let t=e.trim();if(!t)return!1;try{let e=typeof window<`u`?window.location.origin:`https://localhost`,r=new URL(t,e);if(r.protocol!==`https:`&&r.protocol!==`http:`)return!1;if(r.origin===e)return!0;let i=r.hostname.toLowerCase();return n.some(e=>i===e||i.endsWith(`.${e}`))}catch{return!1}},i=e=>{let t=e.trim().toLowerCase();return t?!(t.startsWith(`javascript:`)||t.startsWith(`data:`)||t.startsWith(`vbscript:`)):!1},a=(e,t)=>{let n=t?.trim()||``;if(!n)return;let r=Array.from(e.querySelectorAll(`a[href]`));for(let e of r){let t=e.getAttribute(`href`);if(!t)continue;let r=t.trim().toLowerCase();if(!(r.startsWith(`#`)||r.startsWith(`mailto:`)||r.startsWith(`tel:`)||r.startsWith(`sms:`)||r.startsWith(`javascript:`)||r.startsWith(`data:`)))try{let r=new URL(t,n);if(r.origin!==n){e.getAttribute(`target`)===`_blank`&&e.setAttribute(`rel`,`noopener noreferrer`);continue}let i=`${r.pathname}${r.search}${r.hash}`.replace(/^\/deeplink\//,`/`).replace(/^\/openlink\//,`/`);e.setAttribute(`href`,i),e.classList.add(`internal-link`),e.removeAttribute(`target`),e.removeAttribute(`rel`)}catch{}}},o=e=>typeof e==`object`&&!!e&&`then`in e&&typeof e.then==`function`,s=e=>{try{if(typeof window>`u`)return{handled:!1};let t=window?.staffbase?.plugin?.util?.openLink;if(typeof t!=`function`)return{handled:!1};let n=t(e,{}),r;return n&&o(n)&&(r=n),{handled:!0,promise:r}}catch{return{handled:!1}}},c=e=>{if(!e||!i(e))return!1;let t=s(e);if(t.handled){try{let n=typeof window<`u`?window.location.href:void 0,r=setTimeout(()=>{try{let t=typeof window<`u`?window.location.href:void 0,r=typeof document<`u`?document.visibilityState:void 0;n&&t&&n===t&&r===`visible`&&typeof window<`u`&&window.location.assign(e)}catch{}},400);try{t.promise?.then(()=>{clearTimeout(r)}).catch(()=>{})}catch{}}catch{}return!0}try{typeof window<`u`&&window.location.assign(e)}catch{}return!1};exports.getInAppOpenLinkTarget=t,exports.isAllowedIframeSrc=r,exports.isSafeNavigationHref=i,exports.normalizeInAppLinks=a,exports.openStaffbaseAware=c,exports.tryOpenWithStaffbase=s;
|
|
2
|
+
//# sourceMappingURL=links.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.cjs.js","names":[],"sources":["../src/links/stripStaffbaseLinkPrefix.ts","../src/links/getInAppOpenLinkTarget.ts","../src/links/isAllowedIframeSrc.ts","../src/links/isSafeNavigationHref.ts","../src/links/normalizeInAppLinks.ts","../src/links/isPromiseLike.ts","../src/links/tryOpenWithStaffbase.ts","../src/links/openStaffbaseAware.ts"],"sourcesContent":["/**\n * Strips a leading Staffbase link prefix (`/deeplink/` or `/openlink/`) from a\n * URL pathname, leaving the canonical in-app path.\n * @param {string} path - The URL pathname.\n * @returns {string} The path without the Staffbase prefix.\n */\nexport const stripStaffbaseLinkPrefix = (path: string): string => {\n if (path.startsWith('/deeplink/'))\n return `/${path.slice('/deeplink/'.length)}`\n if (path.startsWith('/openlink/'))\n return `/${path.slice('/openlink/'.length)}`\n return path\n}\n","import { stripStaffbaseLinkPrefix } from './stripStaffbaseLinkPrefix'\n\n/**\n * Returns an absolute URL string that is safe to pass into Staffbase's\n * `openLink()`. We intentionally do NOT require `/openlink/`; if the platform\n * strips it, links can still open in-app by delegating to `openLink()`. Ported\n * from staffbase-alerts (identical to unack).\n * @param {string | null | undefined} href - Raw href as found on an <a> element.\n * @param {string} baseUrl - Base URL used to resolve relative hrefs (usually window.location.origin).\n * @returns {string | null} An absolute URL string, or null if it should not be handled.\n */\nexport const getInAppOpenLinkTarget = (\n href: string | null | undefined,\n baseUrl: string,\n): string | null => {\n const trimmed = href?.trim()\n if (!trimmed) return null\n\n // Ignore anchors and non-navigational links.\n if (trimmed.startsWith('#')) return null\n\n // Let the browser handle special protocols.\n const lower = trimmed.toLowerCase()\n if (\n lower.startsWith('mailto:') ||\n lower.startsWith('tel:') ||\n lower.startsWith('sms:') ||\n lower.startsWith('javascript:') ||\n lower.startsWith('data:')\n ) {\n return null\n }\n\n try {\n const base = new URL(baseUrl)\n const url = new URL(trimmed, base.toString())\n\n // Canonicalize same-origin links by stripping Staffbase prefixes, but keep them absolute.\n if (url.origin === base.origin) {\n const cleanedPath = stripStaffbaseLinkPrefix(url.pathname)\n const absolute = new URL(\n `${cleanedPath}${url.search}${url.hash}`,\n base.origin,\n )\n return absolute.toString()\n }\n\n // For cross-origin absolute URLs, return as-is.\n return url.toString()\n } catch {\n return null\n }\n}\n","// Hosts allowed to be framed inside sanitized article content. Article HTML is\n// first-party authored in Staffbase, but it renders inside a session-bearing\n// webview, so iframe sources are constrained to known embed providers plus the\n// Staffbase origin rather than allowing arbitrary framing. Promoted from\n// staffbase-alerts.\nconst ALLOWED_IFRAME_HOST_SUFFIXES = [\n 'youtube.com',\n 'youtube-nocookie.com',\n 'youtu.be',\n 'vimeo.com',\n 'player.vimeo.com',\n 'staffbase.com',\n 'staffbase.rocks',\n]\n\n/**\n * Returns true when an iframe src may be rendered. Same-origin sources are\n * always allowed; cross-origin sources must match the embed allowlist. Pair with\n * sanitizeArticleHtml's `isAllowedIframeSrc` option.\n * @param {string} src - The iframe src attribute value.\n * @returns {boolean} True when the iframe may be kept.\n */\nexport const isAllowedIframeSrc = (src: string): boolean => {\n const trimmed = src.trim()\n if (!trimmed) return false\n\n try {\n const origin =\n typeof window !== 'undefined'\n ? window.location.origin\n : 'https://localhost'\n const url = new URL(trimmed, origin)\n\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return false\n if (url.origin === origin) return true\n\n const host = url.hostname.toLowerCase()\n return ALLOWED_IFRAME_HOST_SUFFIXES.some(\n (suffix) => host === suffix || host.endsWith(`.${suffix}`),\n )\n } catch {\n return false\n }\n}\n","/**\n * Returns true when an href is safe to pass to a real browser navigation\n * (e.g. `window.location.assign`). Blocks scripted/inline schemes that could\n * execute in the session-bearing webview. Relative URLs and http(s)/mailto/tel\n * are allowed. Promoted from staffbase-alerts.\n * @param {string} href - The href to validate.\n * @returns {boolean} True when the href is safe to navigate to.\n */\nexport const isSafeNavigationHref = (href: string): boolean => {\n const trimmed = href.trim().toLowerCase()\n if (!trimmed) return false\n\n return !(\n trimmed.startsWith('javascript:') ||\n trimmed.startsWith('data:') ||\n trimmed.startsWith('vbscript:')\n )\n}\n","/**\n * Normalizes links inside Staffbase-rendered HTML so they behave better in mobile\n * apps. On iOS, absolute same-origin links and/or `target=\"_blank\"` can trigger\n * Safari instead of in-app navigation; for same-origin links we rewrite to\n * relative paths and remove target/rel. Ported from staffbase-alerts (the\n * superset): cross-origin `target=\"_blank\"` links are hardened with\n * `rel=\"noopener noreferrer\"` instead of left untouched. The Staffbase origin is\n * passed in (the lib never reads env).\n * @param {HTMLElement} root - The container whose anchors are normalized.\n * @param {string} staffbaseOrigin - The Staffbase origin used to detect same-origin links.\n * @returns {void}\n */\nexport const normalizeInAppLinks = (\n root: HTMLElement,\n staffbaseOrigin: string,\n): void => {\n const origin = staffbaseOrigin?.trim() || ''\n if (!origin) return\n\n const anchors = Array.from(root.querySelectorAll('a[href]'))\n\n for (const anchor of anchors) {\n const rawHref = anchor.getAttribute('href')\n if (!rawHref) continue\n\n // Skip special protocols and anchors.\n const lower = rawHref.trim().toLowerCase()\n if (\n lower.startsWith('#') ||\n lower.startsWith('mailto:') ||\n lower.startsWith('tel:') ||\n lower.startsWith('sms:') ||\n lower.startsWith('javascript:') ||\n lower.startsWith('data:')\n ) {\n continue\n }\n\n try {\n const url = new URL(rawHref, origin)\n\n // Only rewrite links that point to the same Staffbase origin.\n if (url.origin !== origin) {\n // Harden cross-origin new-tab links against reverse tabnabbing rather\n // than leaving the authored target/rel untouched.\n if (anchor.getAttribute('target') === '_blank') {\n anchor.setAttribute('rel', 'noopener noreferrer')\n }\n continue\n }\n\n // Convert to a relative URL so the mobile app treats it as in-app navigation.\n const relative = `${url.pathname}${url.search}${url.hash}`\n const withoutStaffbasePrefix = relative\n .replace(/^\\/deeplink\\//, '/')\n .replace(/^\\/openlink\\//, '/')\n anchor.setAttribute('href', withoutStaffbasePrefix)\n\n // Staffbase uses this class in various contexts to mark links as internal.\n // Adding it here increases the chance that the iOS app routes the navigation in-app.\n anchor.classList.add('internal-link')\n\n // Avoid iOS opening Safari for \"new window\" navigation.\n anchor.removeAttribute('target')\n anchor.removeAttribute('rel')\n } catch {\n // If parsing fails, leave link as-is.\n }\n }\n}\n","/**\n * Type guard for Promise-like (thenable) values, used to normalize whatever\n * Staffbase's openLink returns.\n * @param {unknown} value - The value to test.\n * @returns {boolean} True when value has a callable then method.\n */\nexport const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n value !== null &&\n typeof value === 'object' &&\n 'then' in value &&\n typeof (value as PromiseLike<unknown>).then === 'function'\n","import type { TryOpenResult } from '../types/links/TryOpenResult'\nimport type { WindowWithStaffbase } from '../types/links/WindowWithStaffbase'\n\nimport { isPromiseLike } from './isPromiseLike'\n\n/**\n * Attempt to open a link using Staffbase's plugin util.openLink if available.\n * Ported from staffbase-alerts (typed Promise-like guard; smart-search used\n * `as any`).\n * @param {string} href - The target URL to open.\n * @returns {TryOpenResult} Whether Staffbase handled it and a promise if available.\n */\nexport const tryOpenWithStaffbase = (href: string): TryOpenResult => {\n try {\n if (typeof window === 'undefined') {\n return { handled: false }\n }\n const w = window as WindowWithStaffbase\n const open = w?.staffbase?.plugin?.util?.openLink\n const hasOpenLink = typeof open === 'function'\n if (!hasOpenLink) return { handled: false }\n // Pass empty options as the second parameter to align with openLink signature\n const maybePromise = open(href, {})\n // If a Promise is returned, normalize it\n let normalizedPromise: Promise<void> | undefined\n if (maybePromise && isPromiseLike(maybePromise)) {\n normalizedPromise = maybePromise as Promise<void>\n }\n return { handled: true, promise: normalizedPromise }\n } catch {\n return { handled: false }\n }\n}\n","import { isSafeNavigationHref } from './isSafeNavigationHref'\nimport { tryOpenWithStaffbase } from './tryOpenWithStaffbase'\n\n/**\n * Try to open with Staffbase; if not available, navigate normally in the same\n * tab. Ported from staffbase-alerts (the superset): it guards with\n * isSafeNavigationHref first, so scripted schemes never reach navigation.\n * @param {string} href - The target URL to open.\n * @returns {boolean} True if Staffbase handled the navigation, otherwise false.\n */\nexport const openStaffbaseAware = (href: string): boolean => {\n if (!href) return false\n\n // Never navigate to scripted/inline schemes (javascript:, data:, vbscript:),\n // which would execute in the session-bearing webview.\n if (!isSafeNavigationHref(href)) return false\n\n const result = tryOpenWithStaffbase(href)\n if (result.handled) {\n // Watchdog: if Staffbase openLink is a no-op, force navigation shortly after\n try {\n const beforeHref =\n typeof window !== 'undefined' ? window.location.href : undefined\n const timer = setTimeout(() => {\n try {\n const afterHref =\n typeof window !== 'undefined' ? window.location.href : undefined\n const visibility =\n typeof document !== 'undefined'\n ? document.visibilityState\n : undefined\n const noChange = beforeHref && afterHref && beforeHref === afterHref\n const stillVisible = visibility === 'visible'\n if (noChange && stillVisible && typeof window !== 'undefined') {\n window.location.assign(href)\n }\n } catch {\n // Ignore watchdog navigation failures.\n }\n }, 400)\n // If the Staffbase promise resolves, cancel the watchdog fallback\n try {\n result.promise\n ?.then(() => {\n clearTimeout(timer)\n })\n .catch(() => {\n // Keep the watchdog active on rejection.\n })\n } catch {\n // Ignore promise-wiring failures.\n }\n } catch {\n // Ignore watchdog setup failures.\n }\n return true\n }\n try {\n if (typeof window !== 'undefined') {\n window.location.assign(href)\n }\n } catch {\n // Ignore navigation failures.\n }\n return false\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/links/stripStaffbaseLinkPrefix.ts
|
|
2
|
+
var e = (e) => e.startsWith("/deeplink/") || e.startsWith("/openlink/") ? `/${e.slice(10)}` : e, t = (t, n) => {
|
|
3
|
+
let r = t?.trim();
|
|
4
|
+
if (!r || r.startsWith("#")) return null;
|
|
5
|
+
let i = r.toLowerCase();
|
|
6
|
+
if (i.startsWith("mailto:") || i.startsWith("tel:") || i.startsWith("sms:") || i.startsWith("javascript:") || i.startsWith("data:")) return null;
|
|
7
|
+
try {
|
|
8
|
+
let t = new URL(n), i = new URL(r, t.toString());
|
|
9
|
+
if (i.origin === t.origin) {
|
|
10
|
+
let n = e(i.pathname);
|
|
11
|
+
return new URL(`${n}${i.search}${i.hash}`, t.origin).toString();
|
|
12
|
+
}
|
|
13
|
+
return i.toString();
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}, n = [
|
|
18
|
+
"youtube.com",
|
|
19
|
+
"youtube-nocookie.com",
|
|
20
|
+
"youtu.be",
|
|
21
|
+
"vimeo.com",
|
|
22
|
+
"player.vimeo.com",
|
|
23
|
+
"staffbase.com",
|
|
24
|
+
"staffbase.rocks"
|
|
25
|
+
], r = (e) => {
|
|
26
|
+
let t = e.trim();
|
|
27
|
+
if (!t) return !1;
|
|
28
|
+
try {
|
|
29
|
+
let e = typeof window < "u" ? window.location.origin : "https://localhost", r = new URL(t, e);
|
|
30
|
+
if (r.protocol !== "https:" && r.protocol !== "http:") return !1;
|
|
31
|
+
if (r.origin === e) return !0;
|
|
32
|
+
let i = r.hostname.toLowerCase();
|
|
33
|
+
return n.some((e) => i === e || i.endsWith(`.${e}`));
|
|
34
|
+
} catch {
|
|
35
|
+
return !1;
|
|
36
|
+
}
|
|
37
|
+
}, i = (e) => {
|
|
38
|
+
let t = e.trim().toLowerCase();
|
|
39
|
+
return t ? !(t.startsWith("javascript:") || t.startsWith("data:") || t.startsWith("vbscript:")) : !1;
|
|
40
|
+
}, a = (e, t) => {
|
|
41
|
+
let n = t?.trim() || "";
|
|
42
|
+
if (!n) return;
|
|
43
|
+
let r = Array.from(e.querySelectorAll("a[href]"));
|
|
44
|
+
for (let e of r) {
|
|
45
|
+
let t = e.getAttribute("href");
|
|
46
|
+
if (!t) continue;
|
|
47
|
+
let r = t.trim().toLowerCase();
|
|
48
|
+
if (!(r.startsWith("#") || r.startsWith("mailto:") || r.startsWith("tel:") || r.startsWith("sms:") || r.startsWith("javascript:") || r.startsWith("data:"))) try {
|
|
49
|
+
let r = new URL(t, n);
|
|
50
|
+
if (r.origin !== n) {
|
|
51
|
+
e.getAttribute("target") === "_blank" && e.setAttribute("rel", "noopener noreferrer");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
let i = `${r.pathname}${r.search}${r.hash}`.replace(/^\/deeplink\//, "/").replace(/^\/openlink\//, "/");
|
|
55
|
+
e.setAttribute("href", i), e.classList.add("internal-link"), e.removeAttribute("target"), e.removeAttribute("rel");
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
}, o = (e) => typeof e == "object" && !!e && "then" in e && typeof e.then == "function", s = (e) => {
|
|
59
|
+
try {
|
|
60
|
+
if (typeof window > "u") return { handled: !1 };
|
|
61
|
+
let t = window?.staffbase?.plugin?.util?.openLink;
|
|
62
|
+
if (typeof t != "function") return { handled: !1 };
|
|
63
|
+
let n = t(e, {}), r;
|
|
64
|
+
return n && o(n) && (r = n), {
|
|
65
|
+
handled: !0,
|
|
66
|
+
promise: r
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return { handled: !1 };
|
|
70
|
+
}
|
|
71
|
+
}, c = (e) => {
|
|
72
|
+
if (!e || !i(e)) return !1;
|
|
73
|
+
let t = s(e);
|
|
74
|
+
if (t.handled) {
|
|
75
|
+
try {
|
|
76
|
+
let n = typeof window < "u" ? window.location.href : void 0, r = setTimeout(() => {
|
|
77
|
+
try {
|
|
78
|
+
let t = typeof window < "u" ? window.location.href : void 0, r = typeof document < "u" ? document.visibilityState : void 0;
|
|
79
|
+
n && t && n === t && r === "visible" && typeof window < "u" && window.location.assign(e);
|
|
80
|
+
} catch {}
|
|
81
|
+
}, 400);
|
|
82
|
+
try {
|
|
83
|
+
t.promise?.then(() => {
|
|
84
|
+
clearTimeout(r);
|
|
85
|
+
}).catch(() => {});
|
|
86
|
+
} catch {}
|
|
87
|
+
} catch {}
|
|
88
|
+
return !0;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
typeof window < "u" && window.location.assign(e);
|
|
92
|
+
} catch {}
|
|
93
|
+
return !1;
|
|
94
|
+
};
|
|
95
|
+
//#endregion
|
|
96
|
+
export { t as getInAppOpenLinkTarget, r as isAllowedIframeSrc, i as isSafeNavigationHref, a as normalizeInAppLinks, c as openStaffbaseAware, s as tryOpenWithStaffbase };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=links.es.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.es.mjs","names":[],"sources":["../src/links/stripStaffbaseLinkPrefix.ts","../src/links/getInAppOpenLinkTarget.ts","../src/links/isAllowedIframeSrc.ts","../src/links/isSafeNavigationHref.ts","../src/links/normalizeInAppLinks.ts","../src/links/isPromiseLike.ts","../src/links/tryOpenWithStaffbase.ts","../src/links/openStaffbaseAware.ts"],"sourcesContent":["/**\n * Strips a leading Staffbase link prefix (`/deeplink/` or `/openlink/`) from a\n * URL pathname, leaving the canonical in-app path.\n * @param {string} path - The URL pathname.\n * @returns {string} The path without the Staffbase prefix.\n */\nexport const stripStaffbaseLinkPrefix = (path: string): string => {\n if (path.startsWith('/deeplink/'))\n return `/${path.slice('/deeplink/'.length)}`\n if (path.startsWith('/openlink/'))\n return `/${path.slice('/openlink/'.length)}`\n return path\n}\n","import { stripStaffbaseLinkPrefix } from './stripStaffbaseLinkPrefix'\n\n/**\n * Returns an absolute URL string that is safe to pass into Staffbase's\n * `openLink()`. We intentionally do NOT require `/openlink/`; if the platform\n * strips it, links can still open in-app by delegating to `openLink()`. Ported\n * from staffbase-alerts (identical to unack).\n * @param {string | null | undefined} href - Raw href as found on an <a> element.\n * @param {string} baseUrl - Base URL used to resolve relative hrefs (usually window.location.origin).\n * @returns {string | null} An absolute URL string, or null if it should not be handled.\n */\nexport const getInAppOpenLinkTarget = (\n href: string | null | undefined,\n baseUrl: string,\n): string | null => {\n const trimmed = href?.trim()\n if (!trimmed) return null\n\n // Ignore anchors and non-navigational links.\n if (trimmed.startsWith('#')) return null\n\n // Let the browser handle special protocols.\n const lower = trimmed.toLowerCase()\n if (\n lower.startsWith('mailto:') ||\n lower.startsWith('tel:') ||\n lower.startsWith('sms:') ||\n lower.startsWith('javascript:') ||\n lower.startsWith('data:')\n ) {\n return null\n }\n\n try {\n const base = new URL(baseUrl)\n const url = new URL(trimmed, base.toString())\n\n // Canonicalize same-origin links by stripping Staffbase prefixes, but keep them absolute.\n if (url.origin === base.origin) {\n const cleanedPath = stripStaffbaseLinkPrefix(url.pathname)\n const absolute = new URL(\n `${cleanedPath}${url.search}${url.hash}`,\n base.origin,\n )\n return absolute.toString()\n }\n\n // For cross-origin absolute URLs, return as-is.\n return url.toString()\n } catch {\n return null\n }\n}\n","// Hosts allowed to be framed inside sanitized article content. Article HTML is\n// first-party authored in Staffbase, but it renders inside a session-bearing\n// webview, so iframe sources are constrained to known embed providers plus the\n// Staffbase origin rather than allowing arbitrary framing. Promoted from\n// staffbase-alerts.\nconst ALLOWED_IFRAME_HOST_SUFFIXES = [\n 'youtube.com',\n 'youtube-nocookie.com',\n 'youtu.be',\n 'vimeo.com',\n 'player.vimeo.com',\n 'staffbase.com',\n 'staffbase.rocks',\n]\n\n/**\n * Returns true when an iframe src may be rendered. Same-origin sources are\n * always allowed; cross-origin sources must match the embed allowlist. Pair with\n * sanitizeArticleHtml's `isAllowedIframeSrc` option.\n * @param {string} src - The iframe src attribute value.\n * @returns {boolean} True when the iframe may be kept.\n */\nexport const isAllowedIframeSrc = (src: string): boolean => {\n const trimmed = src.trim()\n if (!trimmed) return false\n\n try {\n const origin =\n typeof window !== 'undefined'\n ? window.location.origin\n : 'https://localhost'\n const url = new URL(trimmed, origin)\n\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return false\n if (url.origin === origin) return true\n\n const host = url.hostname.toLowerCase()\n return ALLOWED_IFRAME_HOST_SUFFIXES.some(\n (suffix) => host === suffix || host.endsWith(`.${suffix}`),\n )\n } catch {\n return false\n }\n}\n","/**\n * Returns true when an href is safe to pass to a real browser navigation\n * (e.g. `window.location.assign`). Blocks scripted/inline schemes that could\n * execute in the session-bearing webview. Relative URLs and http(s)/mailto/tel\n * are allowed. Promoted from staffbase-alerts.\n * @param {string} href - The href to validate.\n * @returns {boolean} True when the href is safe to navigate to.\n */\nexport const isSafeNavigationHref = (href: string): boolean => {\n const trimmed = href.trim().toLowerCase()\n if (!trimmed) return false\n\n return !(\n trimmed.startsWith('javascript:') ||\n trimmed.startsWith('data:') ||\n trimmed.startsWith('vbscript:')\n )\n}\n","/**\n * Normalizes links inside Staffbase-rendered HTML so they behave better in mobile\n * apps. On iOS, absolute same-origin links and/or `target=\"_blank\"` can trigger\n * Safari instead of in-app navigation; for same-origin links we rewrite to\n * relative paths and remove target/rel. Ported from staffbase-alerts (the\n * superset): cross-origin `target=\"_blank\"` links are hardened with\n * `rel=\"noopener noreferrer\"` instead of left untouched. The Staffbase origin is\n * passed in (the lib never reads env).\n * @param {HTMLElement} root - The container whose anchors are normalized.\n * @param {string} staffbaseOrigin - The Staffbase origin used to detect same-origin links.\n * @returns {void}\n */\nexport const normalizeInAppLinks = (\n root: HTMLElement,\n staffbaseOrigin: string,\n): void => {\n const origin = staffbaseOrigin?.trim() || ''\n if (!origin) return\n\n const anchors = Array.from(root.querySelectorAll('a[href]'))\n\n for (const anchor of anchors) {\n const rawHref = anchor.getAttribute('href')\n if (!rawHref) continue\n\n // Skip special protocols and anchors.\n const lower = rawHref.trim().toLowerCase()\n if (\n lower.startsWith('#') ||\n lower.startsWith('mailto:') ||\n lower.startsWith('tel:') ||\n lower.startsWith('sms:') ||\n lower.startsWith('javascript:') ||\n lower.startsWith('data:')\n ) {\n continue\n }\n\n try {\n const url = new URL(rawHref, origin)\n\n // Only rewrite links that point to the same Staffbase origin.\n if (url.origin !== origin) {\n // Harden cross-origin new-tab links against reverse tabnabbing rather\n // than leaving the authored target/rel untouched.\n if (anchor.getAttribute('target') === '_blank') {\n anchor.setAttribute('rel', 'noopener noreferrer')\n }\n continue\n }\n\n // Convert to a relative URL so the mobile app treats it as in-app navigation.\n const relative = `${url.pathname}${url.search}${url.hash}`\n const withoutStaffbasePrefix = relative\n .replace(/^\\/deeplink\\//, '/')\n .replace(/^\\/openlink\\//, '/')\n anchor.setAttribute('href', withoutStaffbasePrefix)\n\n // Staffbase uses this class in various contexts to mark links as internal.\n // Adding it here increases the chance that the iOS app routes the navigation in-app.\n anchor.classList.add('internal-link')\n\n // Avoid iOS opening Safari for \"new window\" navigation.\n anchor.removeAttribute('target')\n anchor.removeAttribute('rel')\n } catch {\n // If parsing fails, leave link as-is.\n }\n }\n}\n","/**\n * Type guard for Promise-like (thenable) values, used to normalize whatever\n * Staffbase's openLink returns.\n * @param {unknown} value - The value to test.\n * @returns {boolean} True when value has a callable then method.\n */\nexport const isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n value !== null &&\n typeof value === 'object' &&\n 'then' in value &&\n typeof (value as PromiseLike<unknown>).then === 'function'\n","import type { TryOpenResult } from '../types/links/TryOpenResult'\nimport type { WindowWithStaffbase } from '../types/links/WindowWithStaffbase'\n\nimport { isPromiseLike } from './isPromiseLike'\n\n/**\n * Attempt to open a link using Staffbase's plugin util.openLink if available.\n * Ported from staffbase-alerts (typed Promise-like guard; smart-search used\n * `as any`).\n * @param {string} href - The target URL to open.\n * @returns {TryOpenResult} Whether Staffbase handled it and a promise if available.\n */\nexport const tryOpenWithStaffbase = (href: string): TryOpenResult => {\n try {\n if (typeof window === 'undefined') {\n return { handled: false }\n }\n const w = window as WindowWithStaffbase\n const open = w?.staffbase?.plugin?.util?.openLink\n const hasOpenLink = typeof open === 'function'\n if (!hasOpenLink) return { handled: false }\n // Pass empty options as the second parameter to align with openLink signature\n const maybePromise = open(href, {})\n // If a Promise is returned, normalize it\n let normalizedPromise: Promise<void> | undefined\n if (maybePromise && isPromiseLike(maybePromise)) {\n normalizedPromise = maybePromise as Promise<void>\n }\n return { handled: true, promise: normalizedPromise }\n } catch {\n return { handled: false }\n }\n}\n","import { isSafeNavigationHref } from './isSafeNavigationHref'\nimport { tryOpenWithStaffbase } from './tryOpenWithStaffbase'\n\n/**\n * Try to open with Staffbase; if not available, navigate normally in the same\n * tab. Ported from staffbase-alerts (the superset): it guards with\n * isSafeNavigationHref first, so scripted schemes never reach navigation.\n * @param {string} href - The target URL to open.\n * @returns {boolean} True if Staffbase handled the navigation, otherwise false.\n */\nexport const openStaffbaseAware = (href: string): boolean => {\n if (!href) return false\n\n // Never navigate to scripted/inline schemes (javascript:, data:, vbscript:),\n // which would execute in the session-bearing webview.\n if (!isSafeNavigationHref(href)) return false\n\n const result = tryOpenWithStaffbase(href)\n if (result.handled) {\n // Watchdog: if Staffbase openLink is a no-op, force navigation shortly after\n try {\n const beforeHref =\n typeof window !== 'undefined' ? window.location.href : undefined\n const timer = setTimeout(() => {\n try {\n const afterHref =\n typeof window !== 'undefined' ? window.location.href : undefined\n const visibility =\n typeof document !== 'undefined'\n ? document.visibilityState\n : undefined\n const noChange = beforeHref && afterHref && beforeHref === afterHref\n const stillVisible = visibility === 'visible'\n if (noChange && stillVisible && typeof window !== 'undefined') {\n window.location.assign(href)\n }\n } catch {\n // Ignore watchdog navigation failures.\n }\n }, 400)\n // If the Staffbase promise resolves, cancel the watchdog fallback\n try {\n result.promise\n ?.then(() => {\n clearTimeout(timer)\n })\n .catch(() => {\n // Keep the watchdog active on rejection.\n })\n } catch {\n // Ignore promise-wiring failures.\n }\n } catch {\n // Ignore watchdog setup failures.\n }\n return true\n }\n try {\n if (typeof window !== 'undefined') {\n window.location.assign(href)\n }\n } catch {\n // Ignore navigation failures.\n }\n return false\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns an absolute URL string that is safe to pass into Staffbase's
|
|
3
|
+
* `openLink()`. We intentionally do NOT require `/openlink/`; if the platform
|
|
4
|
+
* strips it, links can still open in-app by delegating to `openLink()`. Ported
|
|
5
|
+
* from staffbase-alerts (identical to unack).
|
|
6
|
+
* @param {string | null | undefined} href - Raw href as found on an <a> element.
|
|
7
|
+
* @param {string} baseUrl - Base URL used to resolve relative hrefs (usually window.location.origin).
|
|
8
|
+
* @returns {string | null} An absolute URL string, or null if it should not be handled.
|
|
9
|
+
*/
|
|
10
|
+
export declare const getInAppOpenLinkTarget: (href: string | null | undefined, baseUrl: string) => string | null;
|
|
11
|
+
//# sourceMappingURL=getInAppOpenLinkTarget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getInAppOpenLinkTarget.d.ts","sourceRoot":"","sources":["../../../src/links/getInAppOpenLinkTarget.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,SAAS,MAAM,KACd,MAAM,GAAG,IAsCX,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { getInAppOpenLinkTarget } from './getInAppOpenLinkTarget';
|
|
2
|
+
export { isAllowedIframeSrc } from './isAllowedIframeSrc';
|
|
3
|
+
export { isSafeNavigationHref } from './isSafeNavigationHref';
|
|
4
|
+
export { normalizeInAppLinks } from './normalizeInAppLinks';
|
|
5
|
+
export { openStaffbaseAware } from './openStaffbaseAware';
|
|
6
|
+
export { tryOpenWithStaffbase } from './tryOpenWithStaffbase';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/links/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when an iframe src may be rendered. Same-origin sources are
|
|
3
|
+
* always allowed; cross-origin sources must match the embed allowlist. Pair with
|
|
4
|
+
* sanitizeArticleHtml's `isAllowedIframeSrc` option.
|
|
5
|
+
* @param {string} src - The iframe src attribute value.
|
|
6
|
+
* @returns {boolean} True when the iframe may be kept.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isAllowedIframeSrc: (src: string) => boolean;
|
|
9
|
+
//# sourceMappingURL=isAllowedIframeSrc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isAllowedIframeSrc.d.ts","sourceRoot":"","sources":["../../../src/links/isAllowedIframeSrc.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,OAqBhD,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guard for Promise-like (thenable) values, used to normalize whatever
|
|
3
|
+
* Staffbase's openLink returns.
|
|
4
|
+
* @param {unknown} value - The value to test.
|
|
5
|
+
* @returns {boolean} True when value has a callable then method.
|
|
6
|
+
*/
|
|
7
|
+
export declare const isPromiseLike: (value: unknown) => value is PromiseLike<unknown>;
|
|
8
|
+
//# sourceMappingURL=isPromiseLike.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isPromiseLike.d.ts","sourceRoot":"","sources":["../../../src/links/isPromiseLike.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,WAAW,CAAC,OAAO,CAIf,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when an href is safe to pass to a real browser navigation
|
|
3
|
+
* (e.g. `window.location.assign`). Blocks scripted/inline schemes that could
|
|
4
|
+
* execute in the session-bearing webview. Relative URLs and http(s)/mailto/tel
|
|
5
|
+
* are allowed. Promoted from staffbase-alerts.
|
|
6
|
+
* @param {string} href - The href to validate.
|
|
7
|
+
* @returns {boolean} True when the href is safe to navigate to.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isSafeNavigationHref: (href: string) => boolean;
|
|
10
|
+
//# sourceMappingURL=isSafeNavigationHref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isSafeNavigationHref.d.ts","sourceRoot":"","sources":["../../../src/links/isSafeNavigationHref.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,OASnD,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes links inside Staffbase-rendered HTML so they behave better in mobile
|
|
3
|
+
* apps. On iOS, absolute same-origin links and/or `target="_blank"` can trigger
|
|
4
|
+
* Safari instead of in-app navigation; for same-origin links we rewrite to
|
|
5
|
+
* relative paths and remove target/rel. Ported from staffbase-alerts (the
|
|
6
|
+
* superset): cross-origin `target="_blank"` links are hardened with
|
|
7
|
+
* `rel="noopener noreferrer"` instead of left untouched. The Staffbase origin is
|
|
8
|
+
* passed in (the lib never reads env).
|
|
9
|
+
* @param {HTMLElement} root - The container whose anchors are normalized.
|
|
10
|
+
* @param {string} staffbaseOrigin - The Staffbase origin used to detect same-origin links.
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
export declare const normalizeInAppLinks: (root: HTMLElement, staffbaseOrigin: string) => void;
|
|
14
|
+
//# sourceMappingURL=normalizeInAppLinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeInAppLinks.d.ts","sourceRoot":"","sources":["../../../src/links/normalizeInAppLinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC9B,MAAM,WAAW,EACjB,iBAAiB,MAAM,KACtB,IAsDF,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Try to open with Staffbase; if not available, navigate normally in the same
|
|
3
|
+
* tab. Ported from staffbase-alerts (the superset): it guards with
|
|
4
|
+
* isSafeNavigationHref first, so scripted schemes never reach navigation.
|
|
5
|
+
* @param {string} href - The target URL to open.
|
|
6
|
+
* @returns {boolean} True if Staffbase handled the navigation, otherwise false.
|
|
7
|
+
*/
|
|
8
|
+
export declare const openStaffbaseAware: (href: string) => boolean;
|
|
9
|
+
//# sourceMappingURL=openStaffbaseAware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openStaffbaseAware.d.ts","sourceRoot":"","sources":["../../../src/links/openStaffbaseAware.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,OAuDjD,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips a leading Staffbase link prefix (`/deeplink/` or `/openlink/`) from a
|
|
3
|
+
* URL pathname, leaving the canonical in-app path.
|
|
4
|
+
* @param {string} path - The URL pathname.
|
|
5
|
+
* @returns {string} The path without the Staffbase prefix.
|
|
6
|
+
*/
|
|
7
|
+
export declare const stripStaffbaseLinkPrefix: (path: string) => string;
|
|
8
|
+
//# sourceMappingURL=stripStaffbaseLinkPrefix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripStaffbaseLinkPrefix.d.ts","sourceRoot":"","sources":["../../../src/links/stripStaffbaseLinkPrefix.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MAMvD,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TryOpenResult } from '../types/links/TryOpenResult';
|
|
2
|
+
/**
|
|
3
|
+
* Attempt to open a link using Staffbase's plugin util.openLink if available.
|
|
4
|
+
* Ported from staffbase-alerts (typed Promise-like guard; smart-search used
|
|
5
|
+
* `as any`).
|
|
6
|
+
* @param {string} href - The target URL to open.
|
|
7
|
+
* @returns {TryOpenResult} Whether Staffbase handled it and a promise if available.
|
|
8
|
+
*/
|
|
9
|
+
export declare const tryOpenWithStaffbase: (href: string) => TryOpenResult;
|
|
10
|
+
//# sourceMappingURL=tryOpenWithStaffbase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tryOpenWithStaffbase.d.ts","sourceRoot":"","sources":["../../../src/links/tryOpenWithStaffbase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAKjE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,aAoBnD,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of attempting to open a link through Staffbase's plugin util.openLink.
|
|
3
|
+
*/
|
|
4
|
+
export interface TryOpenResult {
|
|
5
|
+
/** Whether Staffbase handled the open call. */
|
|
6
|
+
handled: boolean;
|
|
7
|
+
/** The promise openLink returned, when it returned one. */
|
|
8
|
+
promise?: Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=TryOpenResult.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TryOpenResult.d.ts","sourceRoot":"","sources":["../../../../src/types/links/TryOpenResult.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Window augmented with the Staffbase plugin util surface used for in-app link
|
|
3
|
+
* opening. The whole chain is optional because the host API may be absent.
|
|
4
|
+
*/
|
|
5
|
+
export interface WindowWithStaffbase extends Window {
|
|
6
|
+
staffbase?: {
|
|
7
|
+
plugin?: {
|
|
8
|
+
util?: {
|
|
9
|
+
openLink?: (link: string, opts?: unknown) => unknown;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=WindowWithStaffbase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WindowWithStaffbase.d.ts","sourceRoot":"","sources":["../../../../src/types/links/WindowWithStaffbase.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,mBAAoB,SAAQ,MAAM;IACjD,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE;YACP,IAAI,CAAC,EAAE;gBACL,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;aACrD,CAAA;SACF,CAAA;KACF,CAAA;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@favish/staffbase-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Shared internal/host utilities for Staffbase widgets",
|
|
5
5
|
"author": "Favish <dev@favish.com>",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"types": "./dist/src/html/index.d.ts",
|
|
23
23
|
"import": "./dist/html.es.mjs",
|
|
24
24
|
"require": "./dist/html.cjs.js"
|
|
25
|
+
},
|
|
26
|
+
"./links": {
|
|
27
|
+
"types": "./dist/src/links/index.d.ts",
|
|
28
|
+
"import": "./dist/links.es.mjs",
|
|
29
|
+
"require": "./dist/links.cjs.js"
|
|
25
30
|
}
|
|
26
31
|
},
|
|
27
32
|
"repository": {
|