Package
@walkeros/web-destination-piano (observed in 4.2.1)
Summary
When the Piano destination is configured with loadScript: true, the site and
collectDomain settings are never applied to the Piano Analytics SDK. init
injects the SDK <script> asynchronously and then, in the same synchronous tick,
tries to read window.pa and call pa.setConfigurations(...). Because the script
has not loaded yet, window.pa is undefined, the guard short-circuits, and
setConfigurations is silently skipped.
Events are still delivered later (each push re-resolves window.pa lazily, by
which point the SDK has loaded), but they go out without the configured site
and collectDomain — i.e. to the default/unconfigured collection endpoint. There
is no error or warning, so it fails silently.
Source
In packages/web/destinations/piano/src/index.ts, init is roughly:
init({ config, env }) {
const settings = config.settings || {};
if (config.loadScript) addScript(); // appends <script> — loads ASYNCHRONOUSLY
const pa = resolvePa(env); // reads window.pa immediately — still undefined
if (pa && isDefined(settings.site) && isDefined(settings.collectDomain)) {
pa.setConfigurations({ // never reached on first load
site: settings.site,
collectDomain: settings.collectDomain,
...(isObject(settings.options) ? settings.options : {}),
});
}
return config;
}
addScript() only creates the element and appends it; there is no onload
handler, so the subsequent resolvePa/setConfigurations race the script load
and lose every time on a cold page.
Steps to reproduce
-
Configure the destination with loadScript: true and valid site / collectDomain:
destinations: {
piano: {
code: destinationPiano,
config: {
loadScript: true,
settings: {
site: 123456789,
collectDomain: "https://xxxxxxx.pa-cd.com",
},
},
},
}
-
Load a fresh page (so window.pa is not already present) and trigger any event.
-
Observe the network requests to Piano.
Expected
pa.setConfigurations({ site, collectDomain }) is called once the SDK is ready,
and collection hits use the configured site and collectDomain.
Actual
setConfigurations is never called. window.pa is undefined at the moment
init checks it. Events are sent later, but without the configured site/domain.
Workaround
Load the SDK yourself, wait for onload, then init the destination with
loadScript: false so it configures an already-present window.pa:
await new Promise<void>((resolve, reject) => {
if (window.pa) return resolve();
const s = document.createElement("script");
s.src = "https://tag.aticdn.net/piano-analytics.js";
s.async = true;
s.onload = () => resolve();
s.onerror = () => reject(new Error("Failed to load Piano SDK"));
document.head.appendChild(s);
});
// ...then start the collector with config.loadScript = false
Suggested fix
When loadScript injects the SDK, defer configuration until the script's
onload fires (or pa becomes available) instead of calling setConfigurations
synchronously. For example, have addScript accept a callback / return a promise
and run the setConfigurations block from there:
function addScript(src = PIANO_SDK_URL, onReady?: () => void) {
if (typeof document === "undefined") return;
const script = document.createElement("script");
script.src = src;
script.async = true;
if (onReady) script.onload = () => onReady();
document.head.appendChild(script);
}
init({ config, env }) {
const settings = config.settings || {};
const configure = () => {
const pa = resolvePa(env);
if (pa && isDefined(settings.site) && isDefined(settings.collectDomain)) {
pa.setConfigurations({
site: settings.site,
collectDomain: settings.collectDomain,
...(isObject(settings.options) ? settings.options : {}),
});
}
};
if (config.loadScript) {
addScript(PIANO_SDK_URL, configure); // configure after the SDK loads
} else {
configure(); // pa already present
}
return config;
}
Optionally also guard against missing pa at onload time and warn if site/
collectDomain are absent, so the silent-failure mode is easier to diagnose.
Environment
@walkeros/web-destination-piano: 4.2.1
@walkeros/collector / @walkeros/web-source-browser: 4.2.1
- Next.js (client-side init), but the bug is environment-independent — it is a
synchronous-read-after-async-load race in the destination itself.
Package
@walkeros/web-destination-piano(observed in4.2.1)Summary
When the Piano destination is configured with
loadScript: true, thesiteandcollectDomainsettings are never applied to the Piano Analytics SDK.initinjects the SDK
<script>asynchronously and then, in the same synchronous tick,tries to read
window.paand callpa.setConfigurations(...). Because the scripthas not loaded yet,
window.paisundefined, the guard short-circuits, andsetConfigurationsis silently skipped.Events are still delivered later (each
pushre-resolveswindow.palazily, bywhich point the SDK has loaded), but they go out without the configured
siteand
collectDomain— i.e. to the default/unconfigured collection endpoint. Thereis no error or warning, so it fails silently.
Source
In
packages/web/destinations/piano/src/index.ts,initis roughly:addScript()only creates the element and appends it; there is noonloadhandler, so the subsequent
resolvePa/setConfigurationsrace the script loadand lose every time on a cold page.
Steps to reproduce
Configure the destination with
loadScript: trueand validsite/collectDomain:Load a fresh page (so
window.pais not already present) and trigger any event.Observe the network requests to Piano.
Expected
pa.setConfigurations({ site, collectDomain })is called once the SDK is ready,and collection hits use the configured
siteandcollectDomain.Actual
setConfigurationsis never called.window.paisundefinedat the momentinitchecks it. Events are sent later, but without the configured site/domain.Workaround
Load the SDK yourself, wait for
onload, then init the destination withloadScript: falseso it configures an already-presentwindow.pa:Suggested fix
When
loadScriptinjects the SDK, defer configuration until the script'sonloadfires (orpabecomes available) instead of callingsetConfigurationssynchronously. For example, have
addScriptaccept a callback / return a promiseand run the
setConfigurationsblock from there:Optionally also guard against missing
paatonloadtime and warn ifsite/collectDomainare absent, so the silent-failure mode is easier to diagnose.Environment
@walkeros/web-destination-piano: 4.2.1@walkeros/collector/@walkeros/web-source-browser: 4.2.1synchronous-read-after-async-load race in the destination itself.