fix(analytics): actually load the OpenPanel browser script
analytics.ts called window.op?.(…) but nothing ever defined that global — no script tag
in index.html and @openpanel/web was never imported — so every call was a silent no-op
and product analytics would have stayed empty no matter how the build args were set.
Now, when VITE_OPENPANEL_SCRIPT_URL is configured, the loader installs OpenPanel's
documented queueing shim (window.op becomes a queue) and appends op1.js async; the
existing op("init"/"identify"/"track") calls are queued and drained when it loads, so
ordering cannot break them. Without the URL the behaviour is unchanged (no-op), which
keeps local dev and air-gapped builds silent.
Build args: VITE_OPENPANEL_URL (API base, e.g. https://stats.<host>/api),
VITE_OPENPANEL_CLIENT_ID, VITE_OPENPANEL_SCRIPT_URL (e.g. https://stats.<host>/op1.js).
The client *secret* is deliberately not a frontend arg — it belongs to the API only.
This commit is contained in:
@@ -22,8 +22,31 @@ declare global {
|
||||
|
||||
const OPENPANEL_URL = (import.meta.env?.VITE_OPENPANEL_URL as string | undefined)?.trim();
|
||||
const OPENPANEL_CLIENT_ID = (import.meta.env?.VITE_OPENPANEL_CLIENT_ID as string | undefined)?.trim();
|
||||
const OPENPANEL_SCRIPT_URL = (import.meta.env?.VITE_OPENPANEL_SCRIPT_URL as string | undefined)?.trim();
|
||||
const SENTRY_DSN = (import.meta.env?.VITE_SENTRY_DSN as string | undefined)?.trim();
|
||||
|
||||
/**
|
||||
* Load the OpenPanel browser script (op1.js) and its queueing shim.
|
||||
*
|
||||
* The shim is OpenPanel's documented snippet: `window.op` becomes a queue while the
|
||||
* script is in flight, so `op("init", …)` can be called immediately and the real
|
||||
* script drains the queue on load. Without it, every `window.op?.()` call here is a
|
||||
* silent no-op — which is what happened before this loader existed.
|
||||
*/
|
||||
function loadOpenPanelScript(src: string): void {
|
||||
if (document.querySelector("script[data-openpanel]")) return;
|
||||
const shim = document.createElement("script");
|
||||
shim.textContent =
|
||||
'window.op=window.op||function(){var n=[];return new Proxy(function(){arguments.length&&n.push([].slice.call(arguments))},{get:function(t,r){return"q"===r?n:function(){n.push([r].concat([].slice.call(arguments)))}},has:function(t,r){return"q"===r}}) }();';
|
||||
document.head.appendChild(shim);
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.dataset.openpanel = "1";
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
export function initAnalytics(): void {
|
||||
if (SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
@@ -35,6 +58,7 @@ export function initAnalytics(): void {
|
||||
}
|
||||
if (OPENPANEL_URL && OPENPANEL_CLIENT_ID) {
|
||||
try {
|
||||
if (OPENPANEL_SCRIPT_URL) loadOpenPanelScript(OPENPANEL_SCRIPT_URL);
|
||||
window.op?.("init", {
|
||||
apiUrl: OPENPANEL_URL,
|
||||
clientId: OPENPANEL_CLIENT_ID,
|
||||
|
||||
Reference in New Issue
Block a user