{"_id":"@404sl/session-replay-integration","_rev":"5-d776260e4c9d77a791a287090bdadcea","name":"@404sl/session-replay-integration","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@404sl/session-replay-integration","version":"0.1.0","keywords":["bug-report","session-replay","chrome-extension","feedback"],"license":"MIT","_id":"@404sl/session-replay-integration@0.1.0","maintainers":[{"name":"railsblueprint","email":"info@railsblueprint.com"}],"homepage":"https://session-replay.com","bugs":{"url":"https://github.com/404sl/session-replay-integration/issues"},"dist":{"shasum":"19656e52e4dc6a343e3d83962c3957b980e6472e","tarball":"https://registry.npmjs.org/@404sl/session-replay-integration/-/session-replay-integration-0.1.0.tgz","fileCount":9,"integrity":"sha512-S/FfbvNt/e+IrvLVOAmmVBBT/lYllj9sRG8b5uGc/Eu1mYl437IUDX6iXnz+XGsZR0Yy2xxD/HtN/xkaev04zg==","signatures":[{"sig":"MEYCIQDZMHac8siFD4YUoXFOArtjNlBXEfBsQ5WlGge2IG6KnwIhAJZ0EwDiLoTjbx62LQ+dTxmced9aP1imUfK0uX8R8ge4","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":156222},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./copy":"./src/copy.js","./button":"./src/button.js","./splash":"./src/splash.js","./dist/session-replay.js":"./dist/session-replay.js"},"gitHead":"2e4fea02154e62ee17819c420f6abb665622d0c7","scripts":{"test":"node --test","build":"node build.mjs"},"_npmUser":{"name":"railsblueprint","email":"info@railsblueprint.com"},"repository":{"url":"git+https://github.com/404sl/session-replay-integration.git","type":"git"},"_npmVersion":"10.9.2","description":"A \"report a bug\" button for your own site, wired to the Session Replay extension.","directories":{},"sideEffects":["./dist/session-replay.js"],"_nodeVersion":"23.4.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/session-replay-integration_0.1.0_1786953142690_0.21564954883154086","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@404sl/session-replay-integration","version":"0.2.0","keywords":["bug-report","session-replay","chrome-extension","feedback"],"license":"MIT","_id":"@404sl/session-replay-integration@0.2.0","maintainers":[{"name":"railsblueprint","email":"info@railsblueprint.com"}],"homepage":"https://session-replay.com","bugs":{"url":"https://github.com/404sl/session-replay-integration/issues"},"dist":{"shasum":"bd8541e72c19c07f129ef3da3f99aa5622ea8b26","tarball":"https://registry.npmjs.org/@404sl/session-replay-integration/-/session-replay-integration-0.2.0.tgz","fileCount":12,"integrity":"sha512-p+4x6kojN+gF0UyZfxnGTRehHOanWQZQ3vuWP0WS15hINLveK+99c+zlKQiBVGRBoAq2/vBUuEsc2SqUhCLYGg==","signatures":[{"sig":"MEUCIEd4orbTadWkvpYHgVpzwa/xswlAYfGQYiAehDpGjdK1AiEA8tnHxtx/ZAwdMMVK/7QGJZua6SG+KAuWakSTHLSM29k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":188008},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./copy":"./src/copy.js","./button":"./src/button.js","./splash":"./src/splash.js","./styles":"./src/styles.js","./dist/session-replay.js":"./dist/session-replay.js","./dist/session-replay.css":"./dist/session-replay.css"},"gitHead":"17a93d83546460e3e0f00a9a2c0c8dca511b9080","scripts":{"test":"node --test","build":"node build.mjs"},"_npmUser":{"name":"railsblueprint","email":"info@railsblueprint.com"},"repository":{"url":"git+https://github.com/404sl/session-replay-integration.git","type":"git"},"_npmVersion":"10.9.2","description":"A \"report a bug\" button for your own site, wired to the Session Replay extension.","directories":{},"sideEffects":["./dist/session-replay.js"],"_nodeVersion":"23.4.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/session-replay-integration_0.2.0_1786962903729_0.12171807075435437","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@404sl/session-replay-integration","version":"0.3.0","keywords":["bug-report","session-replay","chrome-extension","feedback"],"license":"MIT","_id":"@404sl/session-replay-integration@0.3.0","maintainers":[{"name":"railsblueprint","email":"info@railsblueprint.com"}],"homepage":"https://session-replay.com","bugs":{"url":"https://github.com/404sl/session-replay-integration/issues"},"dist":{"shasum":"26479676af5e0a08b494e4c58bb52c7289af95a1","tarball":"https://registry.npmjs.org/@404sl/session-replay-integration/-/session-replay-integration-0.3.0.tgz","fileCount":13,"integrity":"sha512-9P6BI0N6CBskT5WjHhIsxLbP3YWpJZ5baheoadyRoWi6cZVMM4VkLDgTLq2j+071Qj4OkffdeXjPVbV5R53eiw==","signatures":[{"sig":"MEYCIQDvtTuYlBH8gZ2eOEzs7AavvdH4r7PpmZeZoGG30EApCwIhANwJ7ZnCAWJsuTD7Bjo6attfHNkt3HyDn4+uo+uaIoII","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":210652},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./copy":"./src/copy.js","./button":"./src/button.js","./splash":"./src/splash.js","./styles":"./src/styles.js","./dist/session-replay.js":"./dist/session-replay.js","./dist/session-replay.css":"./dist/session-replay.css"},"gitHead":"5eee892e816bdc704da294ba4b076328b6dabd97","scripts":{"test":"node --test","build":"node build.mjs"},"_npmUser":{"name":"railsblueprint","email":"info@railsblueprint.com"},"repository":{"url":"git+https://github.com/404sl/session-replay-integration.git","type":"git"},"_npmVersion":"10.9.2","description":"A \"report a bug\" button for your own site, wired to the Session Replay extension.","directories":{},"sideEffects":["./dist/session-replay.js"],"_nodeVersion":"23.4.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/session-replay-integration_0.3.0_1787402546562_0.08774436830182086","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@404sl/session-replay-integration","version":"0.3.1","keywords":["bug-report","session-replay","chrome-extension","feedback"],"license":"MIT","_id":"@404sl/session-replay-integration@0.3.1","maintainers":[{"name":"railsblueprint","email":"info@railsblueprint.com"}],"homepage":"https://session-replay.com","bugs":{"url":"https://github.com/404sl/session-replay-integration/issues"},"dist":{"shasum":"e13c204e60f3a2195dc89aae037f6567405ae3c4","tarball":"https://registry.npmjs.org/@404sl/session-replay-integration/-/session-replay-integration-0.3.1.tgz","fileCount":13,"integrity":"sha512-gcJJxY169994zDoJZy5hcFaM6YysdRY0BiMTmm00R3bEfFt5JpfWNM2eNHB76b0EFrRDPSbm63PCDyExq/2FeA==","signatures":[{"sig":"MEUCIQDEsRSsaHWJV+6e9mGD5O1eujXPW4/9oJ0qy/c1uWKNiwIgIGVMgtispU66EZ339cd2P+reLS1fzfa3/P/S4Wgw3o8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":213942},"main":"src/index.js","type":"module","module":"src/index.js","exports":{".":"./src/index.js","./copy":"./src/copy.js","./button":"./src/button.js","./splash":"./src/splash.js","./styles":"./src/styles.js","./dist/session-replay.js":"./dist/session-replay.js","./dist/session-replay.css":"./dist/session-replay.css"},"gitHead":"28c664f3963fd9342f0cdb7b2a8d80ec744b602a","scripts":{"test":"node --test","build":"node build.mjs"},"_npmUser":{"name":"railsblueprint","email":"info@railsblueprint.com"},"repository":{"url":"git+https://github.com/404sl/session-replay-integration.git","type":"git"},"_npmVersion":"10.9.2","description":"A \"report a bug\" button for your own site, wired to the Session Replay extension.","directories":{},"sideEffects":["./dist/session-replay.js"],"_nodeVersion":"23.4.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/session-replay-integration_0.3.1_1787406894022_0.026293397407559205","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@404sl/session-replay-integration","version":"0.4.0","description":"A \"report a bug\" button for your own site, wired to the Session Replay extension.","type":"module","main":"src/index.js","module":"src/index.js","exports":{".":"./src/index.js","./dist/session-replay.js":"./dist/session-replay.js","./button":"./src/button.js","./splash":"./src/splash.js","./copy":"./src/copy.js","./dist/session-replay.css":"./dist/session-replay.css","./styles":"./src/styles.js"},"scripts":{"build":"node build.mjs","test":"node --test"},"keywords":["bug-report","session-replay","chrome-extension","feedback"],"repository":{"type":"git","url":"git+https://github.com/404sl/session-replay-integration.git"},"homepage":"https://session-replay.com","license":"MIT","sideEffects":["./dist/session-replay.js"],"_id":"@404sl/session-replay-integration@0.4.0","gitHead":"26b70a9459d46c4185f5eca22126e68126dc8545","bugs":{"url":"https://github.com/404sl/session-replay-integration/issues"},"_nodeVersion":"23.4.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-mlwPKUDsXrNPQmjotcw/3wJZuSqR5+WSsvvxD9OQSOK0vvfNcgmlkH9JO4T5EfuCwAsJSBidpZUUYpDSQJ+f4g==","shasum":"e9392ec96c85f168de171276eed4739a78262428","tarball":"https://registry.npmjs.org/@404sl/session-replay-integration/-/session-replay-integration-0.4.0.tgz","fileCount":13,"unpackedSize":218776,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAmC4dlmuxdjK4fwJOnJsrVR6mSlLbuaBGkRZrJ3lApOAiBVZKRAsvB+XM+qBj9jyR6+idCKxBNkbsRWP7f4CCHMEQ=="}]},"_npmUser":{"name":"railsblueprint","email":"info@railsblueprint.com"},"directories":{},"maintainers":[{"name":"railsblueprint","email":"info@railsblueprint.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/session-replay-integration_0.4.0_1787608669294_0.4528438329087128"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-17T07:52:22.483Z","modified":"2026-08-24T21:57:49.592Z","0.1.0":"2026-08-17T07:52:22.871Z","0.2.0":"2026-08-17T10:35:03.875Z","0.3.0":"2026-08-22T12:42:26.709Z","0.3.1":"2026-08-22T13:54:54.157Z","0.4.0":"2026-08-24T21:57:49.452Z"},"bugs":{"url":"https://github.com/404sl/session-replay-integration/issues"},"license":"MIT","homepage":"https://session-replay.com","keywords":["bug-report","session-replay","chrome-extension","feedback"],"repository":{"type":"git","url":"git+https://github.com/404sl/session-replay-integration.git"},"description":"A \"report a bug\" button for your own site, wired to the Session Replay extension.","maintainers":[{"name":"railsblueprint","email":"info@railsblueprint.com"}],"readme":"# Session Replay integration\n\nA **\"Report a bug\"** button for your own site.\n\nSomebody who has just hit a problem should not have to know that a browser extension\nexists, find its icon in a toolbar, and work out that it applies to them. They should be\nable to press the thing that says *report a bug*.\n\nThat is all this does. It opens the [Session Replay][site] extension when the visitor has\nit, and explains where to get it when they do not.\n\n## Install\n\n### A script tag\n\n```html\n<script src=\"https://session-replay.com/integration/session-replay-0.4.0.js\" defer></script>\n\n<button data-sr-trigger>Report a bug</button>\n```\n\nAny element with `data-sr-trigger` becomes a trigger. Style it however you like — this\nlibrary never touches how your button looks. That is the path for a site that wants its own\ndesign; the placeholder above is the path for a site that wants none.\n\n### npm\n\n```sh\nnpm install @404sl/session-replay-integration\n```\n\n```js\nimport { init } from '@404sl/session-replay-integration';\n\ninit(); // wires every [data-sr-trigger] on the page\n```\n\nImporting the module does **not** wire anything on its own. A module that reached into the\ndocument when imported would be a poor citizen of somebody else's build; call `init()` when\nyour app is ready. The script-tag build above wires itself, because that is what a script\ntag is for.\n\n## The short version\n\nWrite one empty element. The button that appears is ours - mark, wording, colours and\nstates - and you make no styling decisions at all:\n\n```html\n<head>\n  <script src=\"https://session-replay.com/integration/session-replay-0.4.0.js\" defer></script>\n</head>\n...\n<div data-session-replay-button></div>\n```\n\n## Or write the markup yourself\n\nIf you would rather the button were in your own HTML than added to it by a script - so it is\nin your source, in your server's response, and there before any JavaScript runs - load the\nstylesheet and write the four lines:\n\n```html\n<head>\n  <link rel=\"stylesheet\" href=\"https://session-replay.com/integration/session-replay-0.4.0.css\">\n  <script src=\"https://session-replay.com/integration/session-replay-0.4.0.js\" defer></script>\n</head>\n...\n<div class=\"sr-report\">\n  <button type=\"button\" data-sr-trigger class=\"sr-report-trigger\">\n    <span class=\"sr-report-label\">Report a bug</span>\n  </button>\n  <small class=\"sr-report-by\">Powered by\n    <a class=\"sr-report-link\" href=\"https://session-replay.com/?utm_source=integration&utm_medium=button\"\n       target=\"_blank\" rel=\"noopener\">Session Replay</a></small>\n</div>\n```\n\nThe script still has to load - it is what talks to the extension - but it only wires the\nclick. Both paths produce the same button; the stylesheet is generated from the same file the\ninline styles come from, so they cannot drift apart.\n\nThe stylesheet covers the button where you put it. The floating one stays a JavaScript call,\nbecause pinning something to the corner of a page is a decision rather than a default.\n\n**In the head, with `defer`.** In the head so it is fetched while the page is still\nparsing rather than after it; `defer` so it does not block that parsing and runs once the\nelements it fills exist. It works from the end of the body too — it waits for\n`DOMContentLoaded` when it has to — but the head is a page-load faster.\n\n**The version is in the filename**, so the URL you install never changes contents and is\ncached indefinitely. Taking a new version is a deliberate edit, not something that happens\nto you overnight.\n\nGive the attribute a corner name and it floats there instead:\n\n```html\n<div data-session-replay-button=\"bottom-right\"></div>\n```\n\n## A button, if you want ours\n\nStyle your own trigger, or take a floating one:\n\n```html\n<script src=\"https://session-replay.com/integration/session-replay-0.4.0.js\" defer></script>\n<script>\n  addEventListener('load', () => SessionReplay.mountButton());\n</script>\n```\n\n```js\nimport { mountButton } from '@404sl/session-replay-integration/button';\n\nconst { remove } = mountButton({ position: 'bottom-left' });\n```\n\n`position` is `bottom-right` (default), `bottom-left`, `top-right` or `top-left`. It carries\n`data-sr-trigger` itself, so nothing else needs wiring, and it shrinks to a circle on narrow\nscreens.\n\nNothing mounts it for you. A script tag that put a floating button on somebody's page\nuninvited would be an advert rather than an integration.\n\n## The \"Powered by\" line\n\nThe branded button credits us underneath itself, with a link to the site. That is how people\nfind out this exists, and it is the deal for the button being free to use.\n\nIt is a sibling of the button, never inside it: an `<a>` inside a `<button>` is interactive\ncontent nested in interactive content, which the HTML spec forbids and browsers handle\ninconsistently - usually by making the link unreachable from the keyboard.\n\nTurn it off with `attribution: false`:\n\n```js\nmountButton({ attribution: false });\n```\n\nRemoving it is a **Professional** plan feature. This library cannot check that: it is open\nsource, it runs on your visitor's machine, and it makes no network request - so the option is\nhere for everyone and the plan is between us and you. Sites on Starter that keep the credit\nare the reason the button exists at all.\n\nStyling your own trigger removes it too, since then none of this markup is ours:\n\n```html\n<button data-sr-trigger>Something went wrong?</button>\n```\n\n## What happens when it is pressed\n\n1. It asks the extension, on this page, whether it is there.\n2. If it is, the extension opens its panel and the visitor captures the bug.\n3. If it is not, an overlay explains what it is and links to the Chrome Web Store.\n4. If the browser could never run it — Safari, Firefox — the overlay says so instead of\n   offering an install that would not work.\n\n## It sends nothing anywhere\n\nThere is no network request in this library. No analytics, no beacon, no phone home, no\ncookie. Everything it needs is already in the page it is running in.\n\nDetection is a question asked of the page, not of us: the extension's content script\nanswers a `CustomEvent`. It is deliberately **not** done with `externally_connectable`\nmessaging, because the wildcard form of that would let any site on the internet probe\nwhether a visitor has the extension installed. This channel only answers pages that have\nchosen to load this library.\n\n## API\n\n```js\nimport { report, isAvailable, init, identify } from '@404sl/session-replay-integration';\n\nawait isAvailable();  // is the extension on this page?\nawait report();       // 'opened' | 'blocked' | 'missing' | 'unsupported'\ninit();               // listen, and fill placeholders; safe to call again\nidentify({ email });  // who this is, for whenever a report is made\n```\n\n```js\nimport { renderPlaceholders, mountButton, createButton } from '@404sl/session-replay-integration/button';\n\nrenderPlaceholders(); // fill placeholders rendered since, on their own\nmountButton();        // our floating button, if you want one\ncreateButton();       // the element on its own, to place yourself\n```\n\nThe three that build a button live behind `/button`, so importing `init` does not pull the\nbutton code into your bundle when you have styled your own trigger. In the script-tag build\nthey are all on `window.SessionReplay`, where there is only one file to load anyway.\n\n`report()` is there for sites that would rather trigger from their own code — a menu item,\na keyboard shortcut, an error boundary — than from an element attribute.\n\n### `'blocked'`\n\nChrome only lets an extension open its own side panel in response to a user gesture, and\nwhether a click that began in the page still counts has changed between Chrome versions.\nWhen the panel refuses to open, `report()` returns `'blocked'` and the overlay explains what\nto do instead. It is a real outcome, not a defensive branch.\n\nWhat it says depends on the window. An ordinary browser window is told to open the panel\nfrom the toolbar. A window drawn without browser chrome — an installed PWA, a shortcut\nopened as an app, anything full screen — has no extension toolbar to be told about, so it is\noffered the page's link to carry into a browser tab instead.\n\n## Who hit the bug\n\nA report is worth a great deal more when it says which account made it and which release\nthey were on. Your page knows both; tell us with `identify()`:\n\n```js\nimport { identify } from '@404sl/session-replay-integration';\n\nidentify({\n  email: 'ada@example.com',\n  plan: 'professional',\n  orderId: 'SR-1201',\n  release: '2026.08.18',\n  requestId: 'b1f4c0'\n});\n```\n\n```html\n<script>\n  window.addEventListener('DOMContentLoaded', () => {\n    SessionReplay.identify({ email: 'ada@example.com', release: '2026.08.18' });\n  });\n</script>\n```\n\nThe wait is not decoration. The loader is installed with `defer`, so it does not run until the\ndocument has been parsed, and a call written straight into the markup beside it would reach for\n`window.SessionReplay` while it is still undefined.\n\nThose five are what most sites send, and they are worth naming:\n\n| Key | What it is for |\n| --- | --- |\n| `email` | who is signed in, so a reply can go to them |\n| `plan` | what they are paying for, known before the reply is written |\n| `orderId` | the record the bug is about |\n| `release` | the build the page came from |\n| `requestId` | the server request that rendered it, to join to your own logs |\n\nThey are not a limit. Send whatever you correlate by — a warehouse, a tenant, a feature\nflag — the same way you would with a `sr-data-` meta tag. Keys run to 64 characters and\nvalues to 1024, and a report carries **20 keys in total**. That twenty is one budget shared\nwith your `sr-data-` tags rather than an allowance on top of them: the two are merged with\nthe tags counted first, so past twenty between them it is the pushed keys that fall off the\nend.\n\nKeys are stored under an underscored name: `orderId`, `order-id` and `order_id` are one\nfield, filed as `order_id`. Write them however your code reads best; the report shows one\nrow either way. A meta tag's name is a different matter — it is taken as written after the\n`sr-data-` prefix, so `sr-data-order_id` lands on that same row while `sr-data-order-id`\ngets a row of its own.\n\nPass any subset. Repeat calls **merge**, so a single-page app can add to it as it learns\nmore — sign-in, then a route change — rather than repeating everything each time. A key\ngiven as `null` is dropped, which is what a sign-out wants. A value that is not a plain\nscalar is refused: an object or an array cannot be shown sensibly on a report, so it is\ndropped here rather than travelling to be dropped later.\n\n**We never go looking for any of it.** The library reads nothing out of your DOM — no\nscraping a header for an email, no guessing a plan from a badge. It holds what you pushed\nand hands it over only when the extension asks for it, over the same `CustomEvent` question\nand answer that detection uses. A page that never calls `identify()` says nothing at all,\nand calling it never starts a recording: capturing stays something your visitor does, on\npurpose, by pressing the button.\n\n## Content Security Policy\n\nThe script-tag build needs `script-src https://session-replay.com`. The overlay styles\nelements inline rather than shipping a stylesheet, so no `style-src` entry is required for\na stylesheet — though a policy without `'unsafe-inline'` for styles will need\n`style-src-attr 'unsafe-inline'`.\n\nIf your policy will not allow a third-party script at all, install from npm and bundle it\nwith your own code.\n\n## It speaks the page's language\n\nThe overlay reads `document.documentElement.lang` and answers in it — English, Russian,\nGerman, Spanish, French, Italian or Portuguese, falling back to English for anything else.\nA region tag like `pt-BR` is read as its base language.\n\nThe **page** is asked rather than the browser. `navigator.language` is what the reader\nprefers, which is a different question, and answering it would have the overlay disagree\nwith the paragraph next to it.\n\nSo set `lang` on your `<html>` — worth doing regardless, since screen readers and\nhyphenation need it too.\n\n## Browser support\n\nChromium browsers — Chrome, Edge, Brave, Opera, Arc. The extension is a Chrome extension;\nthis library detects the rest and says so plainly rather than offering an install that\ncannot work.\n\n## Development\n\n```sh\nnpm test     # node's own test runner, no dependencies\nnpm run build  # produces dist/session-replay.js\n```\n\nThere are no dependencies, and there is no bundler. This is a small amount of code that\ngoes on other people's pages, and every dependency it took on would be one they took on\ntoo.\n\n## Licence\n\nMIT. See [LICENSE](LICENSE).\n\n[site]: https://session-replay.com\n","readmeFilename":"README.md"}