@birdie-so/snippet
Easily integrate the Birdie screen recording snippet into your application using modern frameworks like React, Vue, Angular, or plain JavaScript — with full control over initialization, metadata, and event hooks.
⚡ This package is a helper utility for integrating Birdie.
You must have a Birdie account and a validclientIdto use this package.
It does not work on its own — visit https://app.birdie.so to get started.
⚡ The core logic is still loaded from our CDN. This package is just a light wrapper for easier integration and customization.
🚀 Installation
npm install @birdie-so/snippet
# or
yarn add @birdie-so/snippet
✨ Quick Start
React / Vue / Angular / JS
import { initBirdie } from "@birdie-so/snippet";
initBirdie({
clientId: "YOUR_CLIENT_ID", // required
contact: {
email: "[email protected]" // required to get logs capture
}
// Optional metadata available to recordings
metadata: {
user: {
id: "123",
email: "[email protected]",
},
},
// Optional setting to remove response bodies when collecting logs
settings: {
privacy: {
mask_response_body: true
}
},
// Optional hook once Birdie is ready
onReady(birdie) {
// you can register for the following events
birdie.on("recorderOpen", (data) => {
console.log("Recorder tab is opened", data);
});
birdie.on("start", (data) => {
console.log("Recording started", data);
birdie.metadata = { dynamicKey: "value" };
});
birdie.on("pause", (data) => {
console.log("Recording paused", data);
});
birdie.on("resume", (data) => {
console.log("Recording resumed", data);
});
birdie.on("stop", (data) => {
console.log("Recording stopped", data);
});
birdie.on("restart", (data) => {
console.log("Recording restarted", data);
});
birdie.on("captureStarted", (data) => {
console.log("Capturing logs started", data);
});
birdie.on("captureStopped", (data) => {
console.log("Capturing logs stopped", data);
});
birdie.on("recordingSent", (data) => {
// data.link contains the link to the video
console.log("A new recording has been sent", data);
});
birdie.on("recorderClose", (data) => {
console.log("Recorder tab was closed", data);
});
birdie.on("error", (error) => {
console.log("Something went wrong", error);
});
// you can also update data after initialization
birdie.update({
contact: {
email: "[email protected]"
},
metadata: {
parameter: "value"
}
});
}
});
🧠 How It Works
This package:
- Injects the Birdie CDN snippet dynamically using your
clientId. - Sets global
window.birdieSettingsbefore loading. - Registers event callbacks once the Birdie SDK is ready (
window.birdieis available).
Your original snippet like this:
<script>
window.birdieSettings = {
/* ... */
};
</script>
<script src="https://cf.birdie.so/widget/embed/v3/YOUR_CLIENT_ID"
integrity="sha384-7rC8M0J6DWvTouEw7T+c33g2KZtxGjkUe1CxynqsSx9fAHdgzhxr64PaLk7g4un0"
crossorigin="anonymous"></script>
Is now handled via code with initBirdie().
🎬 Recorder launcher (Web SDK)
To open the Birdie recorder from a button, link or element of your own — a
support form, a help center — add the launcher option. It loads the Web SDK
(a separate, signed script from cdn.birdie.so/websdk/) next to the snippet.
Nothing launcher-related is loaded when the option is absent.
initBirdie({
clientId: "YOUR_CLIENT_ID",
contact: { email: "[email protected]" },
launcher: true,
// or: launcher: { trigger: "#record-button", tag: "support-form", autoClose: true, chip: true }
onReady(birdie) {
birdie.on("recorderOpen", () => console.log("recorder window opened"));
birdie.on("start", () => console.log("recording started"));
birdie.on("stop", () => console.log("recording stopped"));
birdie.on("recordingSent", (d) => console.log("sharing link:", d.link));
birdie.on("recorderClose", () => console.log("recorder window closed"));
birdie.launcher.open(); // also close(), toggle(), opened(), closeRecorder()
},
});
Any element with the class birdieWebsdk_open opens the recorder when clicked;
trigger adds a selector of your own. While the recorder window is open, a small
chip bottom right ("Recorder open", "Recording", "Sending") brings it back to
front; it appears only in browsers where the recorder's floating controls cannot
open (Safari), chip: true shows it everywhere, chip: false never.
onReady fires as soon as either the snippet runtime or the launcher is ready,
so it works on a page where only the launcher ends up running (a help center on a
domain the snippet is not authorized for). Events from the launcher carry
source: "websdk"; on a page running both, the same recording can be reported
by each, a second apart.
With launcher set, window.birdie exists from the moment initBirdie()
returns, so birdie.on(...) can be called at once; getBirdieInstance() returns
null until a runtime is actually ready. Without it, nothing changes: the
snippet runtime defines window.birdie as before.
📱 Mobile devices
Birdie does not record on phones or tablets. Since 1.2.3, initBirdie() detects
those devices up front (same rule as the Birdie runtime: iPhone/iPod, Android phones,
iPad, Windows Phone/BlackBerry, and browsers announcing userAgentData.mobile) and
does not load the snippet at all — a mobile page view makes zero requests to Birdie.
Your integration code needs no device check of its own: on mobile, window.birdie
is still provided as an inert object with the same surface (on, off, emit,
update, setContact, setMetadata, contact, metadata, settings, and a
launcher whose open() emits an error event), and
onReady / getBirdieInstance() callbacks still fire. Events simply never occur,
because nothing records. The inert object carries a non-enumerable
__birdieMobileStub: true if you ever need to tell it apart.
Android tablets and desktop browsers behave exactly as before.
🔒 Subresource Integrity (SRI)
initBirdie() loads the Birdie loader from a versioned, immutable URL and pins
it with Subresource Integrity. The browser hashes the file it receives and refuses
to execute it unless the hash matches, so the loader — which carries the public key
that verifies Birdie's signed manifest — cannot be swapped, even by someone who
controls the host serving it.
From v2, the loader also checks the snippet bundle (main.js) against its
sha384 in that signed manifest, and the bundle checks every file it loads after
it, so everything that runs on your page is verified by content.
From v3, the public key the loader carries belongs to a signing key generated
inside AWS KMS, which has never left it, and the loader accepts only that key's
signature. v1 and v2 keep working but trust an older key: upgrade to this
release to move to the new one.
The pin ships inside this package, which your lockfile already pins by content, so the trust anchor does not come from the host it protects.
| Loader version | URL | Integrity (sha384) |
|---|---|---|
v3 | https://cf.birdie.so/widget/embed/v3/{CLIENT_ID} | sha384-7rC8M0J6DWvTouEw7T+c33g2KZtxGjkUe1CxynqsSx9fAHdgzhxr64PaLk7g4un0 |
v2 | https://cf.birdie.so/widget/embed/v2/{CLIENT_ID} | sha384-4c5NWkB8H0CK3H/BaPcvusHR93UZ/JUqI5ofd1qTowWf9zCvnz+nzDNSg+5Opw8E |
v1 | https://cf.birdie.so/widget/embed/v1/{CLIENT_ID} | sha384-UpHM1+1SRGw5d/lkhatup8ShhdrItyJ50qC2whsmcKcl9Xzubnhc+Vq5TXcP8VnJ |
Verify it yourself at any time:
curl -s -H "Origin: https://YOUR_APP_DOMAIN" https://cf.birdie.so/widget/embed/v3/YOUR_CLIENT_ID \
| openssl dgst -sha384 -binary | openssl base64 -A
Snippet updates do not require re-pinning. New Birdie versions ship through the
signed manifest that the loader verifies at runtime; the loader itself changes
rarely, and when it does we publish a new version (v4, …) while the older ones
keep being served — so an application pinned to v1, v2 or v3 keeps working
until it upgrades this package.
The Origin header above matters: without it (or from a domain not yet authorized
for your client ID) CloudFront answers with a short "Birdie is not running here"
script instead of the loader, and the hash will not match.
SRI is optional: if your setup cannot use it, remove the integrity and
crossorigin attributes from the tag above (or load the unversioned URL). The
manifest signature check still applies either way.
🧩 Advanced
Get Birdie instance later
import { getBirdieInstance } from "@birdie-so/snippet";
getBirdieInstance((birdie) => {
birdie.on("start", () => {
console.log("Recording started!");
});
});
Or synchronously:
const birdie = getBirdieInstance();
if (birdie) {
birdie.metadata = { key: "value" };
}
📘 Docs
For full documentation and integration examples, visit our docs page
🛠 Support
Need help? Reach out to us at [email protected]
📄 License
MIT