@jam.dev/recording-links 0.3.0-electron.5 → 0.3.2
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/README.md +25 -10
- package/lib/electron.d.ts +3 -9
- package/lib/electron.js +1 -1
- package/lib/sdk.d.ts +27 -11
- package/lib/sdk.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A TypeScript SDK for loading & initializing Jam Recording Links scripts across b
|
|
|
9
9
|
|
|
10
10
|
## Overview
|
|
11
11
|
|
|
12
|
-
This SDK provides a lightweight interface for coordinating Jam recording sessions across multiple browser contexts. It handles dynamic script loading, cross-tab state synchronization, and recording lifecycle management.
|
|
12
|
+
This SDK provides a lightweight, zero-dependency interface for coordinating Jam recording sessions across multiple browser contexts. It handles dynamic script loading, cross-tab state synchronization, and recording lifecycle management.
|
|
13
13
|
|
|
14
14
|
## Benefits
|
|
15
15
|
|
|
@@ -58,19 +58,34 @@ jam.initialize({
|
|
|
58
58
|
openImmediately: false, // Don't auto-open recorder from URL params
|
|
59
59
|
// OR: "recording-123" to auto-open a recording by ID
|
|
60
60
|
parseJamData: (href) => {
|
|
61
|
-
// Custom logic to extract recording data from URL
|
|
61
|
+
// Custom logic to extract recording data from URL.
|
|
62
|
+
// By default, Jam looks for `jam-` prefixed query parameters.
|
|
63
|
+
// If your app rewrites Recording Links into a different format,
|
|
64
|
+
// you might do something like this:
|
|
62
65
|
const url = new URL(href);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const jamParams = {} as { [K in `jam-${string}`]: string | null; };
|
|
67
|
+
|
|
68
|
+
for (const [key, value] of url.searchParams.entries<>()) {
|
|
69
|
+
if (key.startsWith("my-custom-prefix-")) {
|
|
70
|
+
jamParams[key.replace(/^my-custom-prefix-/, "jam-") as `jam-${string}`] = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return jamParams;
|
|
67
75
|
},
|
|
68
|
-
applyJamData: (data) => {
|
|
69
|
-
// Custom logic to apply recording data to URL
|
|
76
|
+
applyJamData: (data: { [K in `jam-${string}`]: string | null }) => {
|
|
77
|
+
// Custom logic to apply recording data to URL.
|
|
78
|
+
// Similar to the above, if your app uses a non-standard format,
|
|
79
|
+
// you can rewrite the parameters here:
|
|
70
80
|
const url = new URL(window.location.href);
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
for (const [key, value] of Object.entries(data)) {
|
|
83
|
+
if (value) {
|
|
84
|
+
url.searchParams.set(key.replace(/^jam-/, "my-custom-prefix-"), value);
|
|
85
|
+
} else {
|
|
86
|
+
url.searchParams.delete(key);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
|
|
75
90
|
return url.href;
|
|
76
91
|
}
|
package/lib/electron.d.ts
CHANGED
|
@@ -145,7 +145,7 @@ export declare function initialize(config: {
|
|
|
145
145
|
* jam.openRecorder('abc123');
|
|
146
146
|
*
|
|
147
147
|
* // Open with title
|
|
148
|
-
* jam.openRecorder({ recordingId: 'abc123',
|
|
148
|
+
* jam.openRecorder({ recordingId: 'abc123', jamTitle: 'Bug Report' });
|
|
149
149
|
*
|
|
150
150
|
* // Open with URLSearchParams (from protocol handler)
|
|
151
151
|
* const params = new URLSearchParams('jam-recording=abc123&jam-title=Bug+Report');
|
|
@@ -162,10 +162,6 @@ export declare function openUrl(url: string | URL, ses?: Session): [string, Brow
|
|
|
162
162
|
* Data structure representing Jam recording parameters.
|
|
163
163
|
*/
|
|
164
164
|
export interface IJamData {
|
|
165
|
-
/** Jam recording ID */
|
|
166
|
-
readonly recordingId: string;
|
|
167
|
-
/** Optional recording title */
|
|
168
|
-
readonly title: string | undefined | null;
|
|
169
165
|
/**
|
|
170
166
|
* URLSearchParams containing jam-* query parameters.
|
|
171
167
|
* Follows the same convention as URL.searchParams.
|
|
@@ -178,13 +174,11 @@ export interface IJamData {
|
|
|
178
174
|
readonly search: string;
|
|
179
175
|
}
|
|
180
176
|
declare class JamData implements IJamData {
|
|
181
|
-
|
|
182
|
-
readonly title: string | undefined | null;
|
|
177
|
+
private params;
|
|
183
178
|
get searchParams(): URLSearchParams;
|
|
184
179
|
get search(): string;
|
|
185
180
|
constructor(init: URLSearchParams | {
|
|
186
|
-
|
|
187
|
-
title?: string | null;
|
|
181
|
+
[K in `jam-${string}`]: string;
|
|
188
182
|
});
|
|
189
183
|
}
|
|
190
184
|
export declare function isJamRecorder(win: BrowserWindow | null): boolean;
|
package/lib/electron.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{app as e,session as r,webContents as
|
|
1
|
+
import{app as e,session as r,webContents as n,BrowserWindow as o,desktopCapturer as t}from"electron";const s={defaultSession:null,windows:new Map,openRecorderWindow(){throw new Error("Not initialized")},loadRecorderPage(){throw new Error("Not initialized")}},a=(e,r)=>{const s=e.frame,a=s?n.fromFrame(s):null,i=a?o.fromWebContents(a):null;w(i)?t.getSources({types:["screen","window"]}).then(e=>u(e,r,i)).catch(e=>{r({})}):r({})};async function i(n){if(!e.isReady())return e.whenReady().then(()=>i(n));const{defaultSession:o=r.defaultSession,defaultDisplayMediaRequestHandler:t=a}=n;s.defaultSession=o,s.openRecorderWindow=n.openRecorderWindow,s.loadRecorderPage=n.loadRecorderPage,t&&o.setDisplayMediaRequestHandler(t,{useSystemPicker:!0})}function d(e,r){const n=r??s.defaultSession;if(null===n)throw new Error("Cannot open recorder: no `session` found or provided");let o=s.windows.get(n);o||(o=s.openRecorderWindow(n),s.windows.set(n,o),o.on("closed",()=>s.windows.delete(n)));const t="string"==typeof e?new l({"jam-recording":e}):e instanceof l?e:new l(e);return s.loadRecorderPage(o,t).catch(e=>{}),o.isMinimized()&&o.restore(),o.focus(),o}function c(e,r){const n="string"==typeof e?new URL(e):e,o=new URLSearchParams;for(const[e,r]of n.searchParams.entries())e.startsWith("jam-")&&(o.set(e,r),n.searchParams.delete(e));return[n.href,o.has("jam-recording")?d(new l(o),r):null]}class l{get searchParams(){return new URLSearchParams(this.params)}get search(){const e=this.params.toString();return e?`?${e}`:""}constructor(e){const r=new URLSearchParams(e);if(!r.get("jam-recording"))throw new Error("Missing jam-recording parameter");this.params=r}}function w(e){for(const r of s.windows.values())if(r===e)return!0;return!1}function u(e,r,n){const o=n?.getMediaSourceId(),t=e.filter(e=>!e.name.includes("DevTools")&&e.id!==o),s=t.find(e=>e.id.startsWith("window:")),a=t.find(e=>e.id.startsWith("screen:")),i=s||a||t[0];r(i?{video:i,audio:"loopback"}:{})}export{u as handleDisplayMediaRequest,i as initialize,w as isJamRecorder,d as openRecorder,c as openUrl};//# sourceMappingURL=electron.js.map
|
package/lib/sdk.d.ts
CHANGED
|
@@ -22,15 +22,18 @@ type RecorderSingleton = {
|
|
|
22
22
|
* Opens a recorder for the specified recording ID.
|
|
23
23
|
* @param recordingId - The ID of the recording to open
|
|
24
24
|
* @param params - Optional parameters for opening the recorder
|
|
25
|
-
* @param params.jamTitle - Optional title for the recording
|
|
26
25
|
* @param params.removeOnEscape - Whether to remove the opened recorder on Escape presses. Default: true
|
|
27
26
|
* @param params.applyJamData - Custom function to apply recording data to URL
|
|
27
|
+
* @param params.jamParams - Querystring parameters to set when opening the recorder
|
|
28
|
+
* e.g. `jam-title` to set the recording title, `jam-state` to set JWT state
|
|
28
29
|
* @returns Unknown - TODO: expose a public API for opened recorders
|
|
29
30
|
*/
|
|
30
31
|
open(recordingId: string, params?: Pick<InitializeOptions, "applyJamData"> & {
|
|
31
|
-
jamTitle?: string | null;
|
|
32
32
|
/** Whether to remove the opened recorder on Escape presses. Default: true */
|
|
33
33
|
removeOnEscape?: boolean;
|
|
34
|
+
jamParams?: {
|
|
35
|
+
[key: string]: string | null;
|
|
36
|
+
};
|
|
34
37
|
}): unknown;
|
|
35
38
|
};
|
|
36
39
|
type InitializeOptions = {
|
|
@@ -54,12 +57,12 @@ type InitializeOptions = {
|
|
|
54
57
|
*/
|
|
55
58
|
openImmediately?: boolean | string | undefined | null;
|
|
56
59
|
/**
|
|
57
|
-
* Extract a `
|
|
58
|
-
* Defaults to reading the `jam-recording` and `jam-
|
|
60
|
+
* Extract a `jam-recording`, `jam-title`, and other `jam-` params from the provided string.
|
|
61
|
+
* Defaults to reading the `jam-recording`, `jam-title`, and `jam-state` QSPs off the URL.
|
|
59
62
|
*/
|
|
60
63
|
parseJamData?(input: string): SerializableJamData | null;
|
|
61
64
|
/**
|
|
62
|
-
* Applies a `
|
|
65
|
+
* Applies a `jam-recording`, `jam-title`, and other `jam-` params when the JamRecorder is opened or closes.
|
|
63
66
|
* Defaults to setting (or rm'ing) the `jam-recording` and `jam-title` QSPs.
|
|
64
67
|
* Setting a custom `parseJamData` disables this default.
|
|
65
68
|
*/
|
|
@@ -67,14 +70,27 @@ type InitializeOptions = {
|
|
|
67
70
|
/** Selectors to blur on host pages while the Jam Recorder is recording. */
|
|
68
71
|
blurSelectors?: string | string[] | (() => string | string[]);
|
|
69
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Internal options accepted by {@link initialize}. Extends the public
|
|
75
|
+
* {@link InitializeOptions} with `_loadRemoteScript`, a private seam the SDK's own
|
|
76
|
+
* tests use to inject a fake remote-script loader instead of importing from the CDN.
|
|
77
|
+
*
|
|
78
|
+
* `_loadRemoteScript` is deliberately kept off the public {@link InitializeOptions} —
|
|
79
|
+
* consumers must never pass it. It must, however, live on the type that annotates
|
|
80
|
+
* `initialize`'s parameter: destructuring it off the public type instead leaks into the
|
|
81
|
+
* emitted declaration as a property `InitializeOptions` doesn't have, surfacing
|
|
82
|
+
* `TS2339: Property '_loadRemoteScript' does not exist on type 'InitializeOptions'` in
|
|
83
|
+
* every consumer build without `skipLibCheck` (CORE-2500).
|
|
84
|
+
*/
|
|
85
|
+
type InitializeInternalOptions = InitializeOptions & {
|
|
86
|
+
/** @internal Test/override seam — loads `recorder`/`capture` from the Jam CDN. */
|
|
87
|
+
_loadRemoteScript?: (name: ScriptName) => Promise<any>;
|
|
88
|
+
};
|
|
70
89
|
/**
|
|
71
90
|
* Data structure representing Jam recording metadata that can be serialized to/from URLs.
|
|
72
91
|
*/
|
|
73
92
|
export type SerializableJamData = {
|
|
74
|
-
|
|
75
|
-
recordingId: string | null;
|
|
76
|
-
/** The human-readable title of the recording, or null if not present */
|
|
77
|
-
jamTitle: string | null;
|
|
93
|
+
[K in `jam-${string}`]: string | null;
|
|
78
94
|
};
|
|
79
95
|
declare function resetForTesting(): void;
|
|
80
96
|
/** @internal - Reset SDK state for testing (only available in dev builds) */
|
|
@@ -128,7 +144,7 @@ export declare let Recorder: RecorderSingleton | null;
|
|
|
128
144
|
* });
|
|
129
145
|
* ```
|
|
130
146
|
*/
|
|
131
|
-
export declare function initialize({ recorderRefCounter, _loadRemoteScript, ...config }?:
|
|
147
|
+
export declare function initialize({ recorderRefCounter, _loadRemoteScript, ...config }?: InitializeInternalOptions): void;
|
|
132
148
|
/**
|
|
133
149
|
* Loads the Jam recorder module and returns the recorder singleton.
|
|
134
150
|
*
|
|
@@ -159,7 +175,7 @@ export declare function initialize({ recorderRefCounter, _loadRemoteScript, ...c
|
|
|
159
175
|
* });
|
|
160
176
|
*
|
|
161
177
|
* // Use the recorder
|
|
162
|
-
* recorder.open("recording-xyz789", {
|
|
178
|
+
* recorder.open("recording-xyz789", { jamParams: { "jam-title": "Bug Report" } });
|
|
163
179
|
* ```
|
|
164
180
|
*/
|
|
165
181
|
export declare function loadRecorder({ ...config }?: Omit<InitializeOptions, "recorderRefCounter">): Promise<RecorderSingleton>;
|
package/lib/sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function e(){const e=new EventTarget;return{addEventListener(t,n,r){e.addEventListener(t,n,r)},removeEventListener(t,n,r){e.removeEventListener(t,n,r)},dispatch:(t,n)=>e.dispatchEvent(new CustomEvent(t,{detail:n}))}}const t=(t,n=localStorage)=>{const r=`jam:${t}`,i=e();let o=null;try{o=n.getItem(r)}catch(e){}const a={count:Number.parseInt(o??"0",10)||0,addEventListener:i.addEventListener.bind(i),removeEventListener:i.removeEventListener.bind(i),update(e){const t=a.count;switch(e){case"increment":a.count+=1;break;case"decrement":a.count-=1;break;default:a.count=e}if(a.count<0&&(a.count=0),a.count!==t){try{n.setItem(r,`${a.count}`)}catch(e){}Object.assign(a,{count:a.count}),i.dispatch("update",a.count)}return a.count}};return window.addEventListener("storage",e=>{if(e.storageArea===n&&e.key===r){const t=Number.parseInt(e.newValue??"",10);Number.isNaN(t)||t===a.count||(a.count=t,i.dispatch("update",a.count))}}),a},n=e=>{try{const t=new URL(e);
|
|
1
|
+
function e(){const e=new EventTarget;return{addEventListener(t,n,r){e.addEventListener(t,n,r)},removeEventListener(t,n,r){e.removeEventListener(t,n,r)},dispatch:(t,n)=>e.dispatchEvent(new CustomEvent(t,{detail:n}))}}const t=(t,n=localStorage)=>{const r=`jam:${t}`,i=e();let o=null;try{o=n.getItem(r)}catch(e){}const a={count:Number.parseInt(o??"0",10)||0,addEventListener:i.addEventListener.bind(i),removeEventListener:i.removeEventListener.bind(i),update(e){const t=a.count;switch(e){case"increment":a.count+=1;break;case"decrement":a.count-=1;break;default:a.count=e}if(a.count<0&&(a.count=0),a.count!==t){try{n.setItem(r,`${a.count}`)}catch(e){}Object.assign(a,{count:a.count}),i.dispatch("update",a.count)}return a.count}};return window.addEventListener("storage",e=>{if(e.storageArea===n&&e.key===r){const t=Number.parseInt(e.newValue??"",10);Number.isNaN(t)||t===a.count||(a.count=t,i.dispatch("update",a.count))}}),a},n=e=>{try{const t=new URL(e),n={};for(const[e,r]of t.searchParams.entries())e.startsWith("jam-")&&(n[e]=r);return Object.keys(n).length>0?n:null}catch(e){}return null},r={isInitialized:!1},i=void 0,o=e(),a=o.addEventListener.bind(o),c=o.removeEventListener.bind(o),d=()=>r.isInitialized;let s=null;function u({recorderRefCounter:e=t("numRecorders"),_loadRemoteScript:i=m,...o}={}){if(r.isInitialized)throw new Error("SDK already initialized.");Object.assign(r,{isInitialized:!0,recorderRefCounter:e,config:o,_loadRemoteScript:i}),e.count>0?p():e.addEventListener("update",p,{once:!0}),window.addEventListener("popstate",c);const a={apply(e,t,n){const r=Reflect.apply(e,t,n);return c(),r}};function c(){if(s)return;const e=r.config?.parseJamData??n,t="string"==typeof r.config?.openImmediately?r.config.openImmediately:e(window.location.href)?.["jam-recording"];t&&l({openImmediately:!1!==r.config?.openImmediately&&t})}history.pushState=new Proxy(history.pushState,a),history.replaceState=new Proxy(history.replaceState,a),c()}async function l({...e}={}){if(s)return s;if(!r.isInitialized)throw new Error("SDK not initialized. Call initialize() first.");if(({Recorder:s}=await r._loadRemoteScript("recorder")),!s)throw new Error("Failed to load recorder script.");const{openImmediately:t,...n}={...r.config,...e},i="string"==typeof t?t:null;return s.initialize({...n,openImmediately:!i&&(t??!0)}),r.recorderRefCounter.update("increment"),window.addEventListener("pagehide",()=>{r.recorderRefCounter.update("decrement")}),i&&s.open(i,n),s}async function p(){if(!r.isInitialized||!r._loadRemoteScript)throw new Error("SDK not initialized. Call initialize() first.");const{Capture:e}=await r._loadRemoteScript("capture")??{};await(e?.initialize(r.config))}async function m(e){const t=`https://js.jam.dev/${e}.js`,n=await import(
|
|
2
2
|
/* webpackIgnore: true */
|
|
3
3
|
/* @vite-ignore */
|
|
4
4
|
/* @rollup/plugin-dynamic-import-vars ignore */
|