@intray/live 0.5.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/LICENSE +21 -0
- package/README.md +97 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +90 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lowside Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @intray/live
|
|
2
|
+
|
|
3
|
+
Live session watching for [Intray](https://intray.ai). While someone on your team has the Live view open, the SDK streams the signed-in customer's page to them. Nobody watching means nothing is captured. Intray stores no replay.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @intray/live
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## How the parts connect
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
createLiveWatch (browser) -> your backend route -> Intray POST /v1/support/sessions
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The browser never holds the Intray key. Your backend reads the signed-in user from its own session, asks Intray for a short-lived recorder token, and returns it.
|
|
16
|
+
|
|
17
|
+
## Server
|
|
18
|
+
|
|
19
|
+
Create the application in Intray under Live, Manage applications, with your product's exact HTTPS origin. Store its key as `INTRAY_LIVE_KEY`.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// Server only. Take the user and account from your own session.
|
|
23
|
+
export async function bootstrapLiveSession(req: Request, sessionId: string) {
|
|
24
|
+
const user = await requireUser(req);
|
|
25
|
+
|
|
26
|
+
const res = await fetch(process.env.INTRAY_LIVE_BOOTSTRAP_URL!, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${process.env.INTRAY_LIVE_KEY}`,
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
sessionId,
|
|
34
|
+
userId: user.id,
|
|
35
|
+
accountId: user.accountId,
|
|
36
|
+
displayName: user.name,
|
|
37
|
+
origin: process.env.SITE_URL,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) throw new Error("Intray refused the live session");
|
|
41
|
+
return res.json(); // { token, expiresAt, serverUrl, sessionId }
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Do not let browser arguments choose `userId`, `accountId`, or `origin`. Tokens last 120 seconds. The SDK calls `getSession` again, with the same id, on every reconnect and renewal.
|
|
46
|
+
|
|
47
|
+
## Browser
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createLiveWatch } from "@intray/live";
|
|
51
|
+
|
|
52
|
+
const live = createLiveWatch({
|
|
53
|
+
getSession: (sessionId) => api.bootstrapLiveSession({ sessionId }),
|
|
54
|
+
isPageAllowed: () => location.pathname.startsWith("/app"),
|
|
55
|
+
blockSelector: "[data-support-private]",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await live.start();
|
|
59
|
+
|
|
60
|
+
// On logout or account switch
|
|
61
|
+
live.destroy();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Options
|
|
65
|
+
|
|
66
|
+
| Option | Type | Notes |
|
|
67
|
+
| -------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `getSession` | `(sessionId) => Promise<LiveBootstrap>` | Required. Calls your backend route. |
|
|
69
|
+
| `isPageAllowed` | `() => boolean` | Checked as the page changes. Capture pauses while it returns false. |
|
|
70
|
+
| `blockSelector` | `string` | Added to the default block list, which already has `[data-private]`. A blocked element renders as an empty box. |
|
|
71
|
+
| `maskTextSelector` | `string` | Defaults to `*`. All text is masked. |
|
|
72
|
+
| `publicTextSelector` | `string` | Opt-in for reviewed static labels, for example `[data-support-public]`. |
|
|
73
|
+
| `onState` | `(state) => void` | `connecting`, `idle`, `live`, `paused`, `hidden`, `reconnecting`, `unavailable`, `stopped`. |
|
|
74
|
+
| `assistance` | `boolean` | Off by default. Lets an operator request customer-approved clicks on `data-support-control` buttons. |
|
|
75
|
+
| `controlsContainer` | `HTMLElement` | Where the consent prompt mounts. Defaults to a floating prompt. |
|
|
76
|
+
|
|
77
|
+
### Methods
|
|
78
|
+
|
|
79
|
+
`start()`, `stopSharing()`, `resumeSharing()`, `destroy()`. Use `stopSharing` and `resumeSharing` to give customers their own switch. Destroy the instance on logout or account change. Do not reuse it across identities.
|
|
80
|
+
|
|
81
|
+
## Privacy defaults
|
|
82
|
+
|
|
83
|
+
- All text and every input value are masked before they leave the browser.
|
|
84
|
+
- Password and payment fields, canvas, and iframes are never sent.
|
|
85
|
+
- URL query strings and hashes are stripped.
|
|
86
|
+
- `.rr-mask`, `[data-support-mask]`, `[data-live-mask]`, inputs, and editable content stay masked even inside a public marker.
|
|
87
|
+
- Never put `publicTextSelector` markers on a container that can hold customer content.
|
|
88
|
+
|
|
89
|
+
Viewing shows no indicator in your product. Disclose live viewing in your privacy policy and terms.
|
|
90
|
+
|
|
91
|
+
## Bundle
|
|
92
|
+
|
|
93
|
+
ESM only, browser target ES2022. The recorder loads on demand, so `rrweb` stays out of your first chunk. `rrweb` is pinned as a dependency.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Always blocked. A configured `blockSelector` adds to this list and cannot shorten it. */
|
|
2
|
+
declare const DEFAULT_BLOCK_SELECTOR: string;
|
|
3
|
+
/** Pilot default: every text node is masked. */
|
|
4
|
+
declare const DEFAULT_MASK_TEXT_SELECTOR = "*";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What the host backend returns from `POST /v1/support/sessions`.
|
|
8
|
+
*
|
|
9
|
+
* Declared here, not re-exported, because the protocol package is private and
|
|
10
|
+
* a published type file cannot import it. The check below fails the build when
|
|
11
|
+
* the two drift.
|
|
12
|
+
*/
|
|
13
|
+
type LiveBootstrap = {
|
|
14
|
+
token: string;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
serverUrl: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type LiveWatchState = "connecting"
|
|
21
|
+
/** Connected and sharing. Nobody is watching, so nothing is captured. */
|
|
22
|
+
| "idle"
|
|
23
|
+
/** A viewer is present and the page is being captured. */
|
|
24
|
+
| "live"
|
|
25
|
+
/** The user stopped sharing. */
|
|
26
|
+
| "paused"
|
|
27
|
+
/** The tab is hidden or the page is not allowed. */
|
|
28
|
+
| "hidden" | "reconnecting"
|
|
29
|
+
/** The live service refused this session. No further attempts are made. */
|
|
30
|
+
| "unavailable" | "stopped";
|
|
31
|
+
type LiveWatchOptions = {
|
|
32
|
+
getSession: (sessionId: string) => Promise<LiveBootstrap>;
|
|
33
|
+
onState?: (state: string) => void;
|
|
34
|
+
/** Place the control consent prompt in the product layout instead of an overlay. */
|
|
35
|
+
controlsContainer?: HTMLElement;
|
|
36
|
+
/** Explicitly enable customer-approved clicks on data-support-control buttons. Defaults off. */
|
|
37
|
+
assistance?: boolean;
|
|
38
|
+
blockSelector?: string;
|
|
39
|
+
maskTextSelector?: string;
|
|
40
|
+
publicTextSelector?: string;
|
|
41
|
+
isPageAllowed?: () => boolean;
|
|
42
|
+
};
|
|
43
|
+
type LiveWatch = {
|
|
44
|
+
start(): Promise<void>;
|
|
45
|
+
stopSharing(): void;
|
|
46
|
+
resumeSharing(): void;
|
|
47
|
+
destroy(): void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Shares the current page with Intray staff while one of them is watching.
|
|
52
|
+
*
|
|
53
|
+
* `getSession` must call the host app's authenticated backend, which returns a
|
|
54
|
+
* short-lived recorder token for the given session id. It is called again, with
|
|
55
|
+
* the same id, on every reconnect and renewal.
|
|
56
|
+
*/
|
|
57
|
+
declare function createLiveWatch(options: LiveWatchOptions): LiveWatch;
|
|
58
|
+
|
|
59
|
+
export { DEFAULT_BLOCK_SELECTOR, DEFAULT_MASK_TEXT_SELECTOR, type LiveBootstrap, type LiveWatch, type LiveWatchOptions, type LiveWatchState, createLiveWatch };
|