@kharko/dozor 0.1.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/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
'use strict';var rrweb=require('rrweb');function d(){return {url:location.href,referrer:document.referrer,userAgent:navigator.userAgent,screenWidth:screen.width,screenHeight:screen.height,language:navigator.language}}var l="dozor_session_id";function c(){try{let t=sessionStorage.getItem(l);if(t)return t}catch{}let n=crypto.randomUUID();try{sessionStorage.setItem(l,n);}catch{}return n}var a=class{constructor(t,e){this.endpoint=t,this.apiKey=e;}async send(t,e,s){let r={sessionId:t,events:e};s&&(r.metadata=s);let f=JSON.stringify(r);for(let o=0;o<3;o++){try{let h=await fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Dozor-Public-Key":this.apiKey},body:f,keepalive:!0});if(h.ok)return !0;if(h.status>=400&&h.status<500)return !1}catch{}o<2&&await u(1e3*2**o);}return false}sendKeepalive(t,e){if(e.length===0)return;let s={sessionId:t,events:e};try{fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Dozor-Public-Key":this.apiKey},body:JSON.stringify(s),keepalive:!0}).catch(()=>{});}catch{}}};function u(n){return new Promise(t=>setTimeout(t,n))}var v="https://dozor.kharko.dev/api/ingest",g=5e3,y=50,i=class i{constructor(t){this.buffer=[];this.metadataSent=false;this.flushTimer=null;this.stopRecording=null;let e=t.endpoint??v;this.batchSize=t.batchSize??y;let s=t.flushInterval??g;this.transport=new a(e,t.apiKey),this.sessionId=c(),this.metadata=d(),this.stopRecording=rrweb.record({emit:r=>this.onEvent(r)})??null,this.flushTimer=setInterval(()=>this.flush(),s),addEventListener("beforeunload",()=>this.onBeforeUnload()),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush();});}static init(t){return i.instance||(i.instance=new i(t)),i.instance}stop(){this.stopRecording&&(this.stopRecording(),this.stopRecording=null),this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.flush(),i.instance=null;}onEvent(t){this.buffer.push(t),this.buffer.length>=this.batchSize&&this.flush();}flush(){if(this.buffer.length===0)return;let t=this.buffer;this.buffer=[];let e=this.metadataSent?void 0:this.metadata??void 0;e&&(this.metadataSent=true),this.transport.send(this.sessionId,t,e).catch(()=>{});}onBeforeUnload(){if(this.buffer.length===0)return;let t=this.buffer;this.buffer=[],this.transport.sendKeepalive(this.sessionId,t);}};i.instance=null;var p=i;
|
|
2
|
+
exports.Dozor=p;//# sourceMappingURL=index.cjs.map
|
|
3
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/metadata.ts","../src/session.ts","../src/transport.ts","../src/recorder.ts"],"names":["collectMetadata","SESSION_KEY","getSessionId","existing","id","Transport","endpoint","apiKey","sessionId","events","metadata","payload","body","attempt","res","sleep","ms","resolve","DEFAULT_ENDPOINT","DEFAULT_FLUSH_INTERVAL","DEFAULT_BATCH_SIZE","_Dozor","options","flushInterval","record","event","Dozor"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SessionMetadata } from \"./types.js\";\n\n/** Collect session metadata from the browser environment. */\nexport function collectMetadata(): SessionMetadata {\n return {\n url: location.href,\n referrer: document.referrer,\n userAgent: navigator.userAgent,\n screenWidth: screen.width,\n screenHeight: screen.height,\n language: navigator.language,\n };\n}\n","const SESSION_KEY = \"dozor_session_id\";\n\n/** Get or create a session ID persisted in sessionStorage for SPA continuity. */\nexport function getSessionId(): string {\n try {\n const existing = sessionStorage.getItem(SESSION_KEY);\n if (existing) return existing;\n } catch {\n // sessionStorage unavailable (SSR, iframe sandbox, etc.)\n }\n\n const id = crypto.randomUUID();\n\n try {\n sessionStorage.setItem(SESSION_KEY, id);\n } catch {\n // best-effort persistence\n }\n\n return id;\n}\n","import type { eventWithTime } from \"rrweb\";\nimport type { IngestPayload, SessionMetadata } from \"./types.js\";\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\nexport class Transport {\n private endpoint: string;\n private apiKey: string;\n\n constructor(endpoint: string, apiKey: string) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n }\n\n /** Send a batch of events via fetch with retry. */\n async send(sessionId: string, events: eventWithTime[], metadata?: SessionMetadata): Promise<boolean> {\n const payload: IngestPayload = { sessionId, events };\n if (metadata) payload.metadata = metadata;\n\n const body = JSON.stringify(payload);\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const res = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Dozor-Public-Key\": this.apiKey,\n },\n body,\n keepalive: true,\n });\n\n if (res.ok) return true;\n\n // Don't retry client errors (400, 401, etc.)\n if (res.status >= 400 && res.status < 500) return false;\n } catch {\n // Network error — retry\n }\n\n if (attempt < MAX_RETRIES - 1) {\n await sleep(BASE_DELAY_MS * 2 ** attempt);\n }\n }\n\n return false;\n }\n\n /** Best-effort send via fetch with keepalive (for page unload). */\n sendKeepalive(sessionId: string, events: eventWithTime[]): void {\n if (events.length === 0) return;\n\n const payload: IngestPayload = { sessionId, events };\n\n try {\n fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Dozor-Public-Key\": this.apiKey,\n },\n body: JSON.stringify(payload),\n keepalive: true,\n }).catch(() => {});\n } catch {\n // best-effort, ignore failures during unload\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { record } from \"rrweb\";\nimport type { eventWithTime } from \"rrweb\";\nimport type { DozorOptions, SessionMetadata } from \"./types.js\";\nimport { collectMetadata } from \"./metadata.js\";\nimport { getSessionId } from \"./session.js\";\nimport { Transport } from \"./transport.js\";\n\nconst DEFAULT_ENDPOINT = \"https://dozor.kharko.dev/api/ingest\";\nconst DEFAULT_FLUSH_INTERVAL = 5_000;\nconst DEFAULT_BATCH_SIZE = 50;\n\nexport class Dozor {\n private static instance: Dozor | null = null;\n\n private transport: Transport;\n private sessionId: string;\n private buffer: eventWithTime[] = [];\n private metadata: SessionMetadata | null;\n private metadataSent = false;\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private stopRecording: (() => void) | null = null;\n private batchSize: number;\n\n private constructor(options: DozorOptions) {\n const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n const flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n\n this.transport = new Transport(endpoint, options.apiKey);\n this.sessionId = getSessionId();\n this.metadata = collectMetadata();\n\n this.stopRecording = record({\n emit: (event) => this.onEvent(event),\n }) ?? null;\n\n this.flushTimer = setInterval(() => this.flush(), flushInterval);\n\n addEventListener(\"beforeunload\", () => this.onBeforeUnload());\n addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") this.flush();\n });\n }\n\n /** Initialize the Dozor recorder. Returns the singleton instance. */\n static init(options: DozorOptions): Dozor {\n if (Dozor.instance) return Dozor.instance;\n Dozor.instance = new Dozor(options);\n return Dozor.instance;\n }\n\n /** Stop recording and flush remaining events. */\n stop(): void {\n if (this.stopRecording) {\n this.stopRecording();\n this.stopRecording = null;\n }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n this.flush();\n Dozor.instance = null;\n }\n\n private onEvent(event: eventWithTime): void {\n this.buffer.push(event);\n\n if (this.buffer.length >= this.batchSize) {\n this.flush();\n }\n }\n\n private flush(): void {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer;\n this.buffer = [];\n\n const metadata = !this.metadataSent ? this.metadata ?? undefined : undefined;\n if (metadata) this.metadataSent = true;\n\n this.transport.send(this.sessionId, events, metadata).catch(() => {});\n }\n\n private onBeforeUnload(): void {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer;\n this.buffer = [];\n\n this.transport.sendKeepalive(this.sessionId, events);\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { eventWithTime } from 'rrweb';
|
|
2
|
+
|
|
3
|
+
interface DozorOptions {
|
|
4
|
+
/** Public API key (dp_...) */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/** Ingest endpoint URL. Defaults to https://dozor.kharko.dev/api/ingest */
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
/** Flush interval in ms. Default: 5000 */
|
|
9
|
+
flushInterval?: number;
|
|
10
|
+
/** Max events per batch before auto-flush. Default: 50 */
|
|
11
|
+
batchSize?: number;
|
|
12
|
+
}
|
|
13
|
+
interface SessionMetadata {
|
|
14
|
+
url: string;
|
|
15
|
+
referrer: string;
|
|
16
|
+
userAgent: string;
|
|
17
|
+
screenWidth: number;
|
|
18
|
+
screenHeight: number;
|
|
19
|
+
language: string;
|
|
20
|
+
}
|
|
21
|
+
interface IngestPayload {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
events: eventWithTime[];
|
|
24
|
+
metadata?: SessionMetadata;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare class Dozor {
|
|
28
|
+
private static instance;
|
|
29
|
+
private transport;
|
|
30
|
+
private sessionId;
|
|
31
|
+
private buffer;
|
|
32
|
+
private metadata;
|
|
33
|
+
private metadataSent;
|
|
34
|
+
private flushTimer;
|
|
35
|
+
private stopRecording;
|
|
36
|
+
private batchSize;
|
|
37
|
+
private constructor();
|
|
38
|
+
/** Initialize the Dozor recorder. Returns the singleton instance. */
|
|
39
|
+
static init(options: DozorOptions): Dozor;
|
|
40
|
+
/** Stop recording and flush remaining events. */
|
|
41
|
+
stop(): void;
|
|
42
|
+
private onEvent;
|
|
43
|
+
private flush;
|
|
44
|
+
private onBeforeUnload;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Dozor, type DozorOptions, type IngestPayload, type SessionMetadata };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { eventWithTime } from 'rrweb';
|
|
2
|
+
|
|
3
|
+
interface DozorOptions {
|
|
4
|
+
/** Public API key (dp_...) */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/** Ingest endpoint URL. Defaults to https://dozor.kharko.dev/api/ingest */
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
/** Flush interval in ms. Default: 5000 */
|
|
9
|
+
flushInterval?: number;
|
|
10
|
+
/** Max events per batch before auto-flush. Default: 50 */
|
|
11
|
+
batchSize?: number;
|
|
12
|
+
}
|
|
13
|
+
interface SessionMetadata {
|
|
14
|
+
url: string;
|
|
15
|
+
referrer: string;
|
|
16
|
+
userAgent: string;
|
|
17
|
+
screenWidth: number;
|
|
18
|
+
screenHeight: number;
|
|
19
|
+
language: string;
|
|
20
|
+
}
|
|
21
|
+
interface IngestPayload {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
events: eventWithTime[];
|
|
24
|
+
metadata?: SessionMetadata;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare class Dozor {
|
|
28
|
+
private static instance;
|
|
29
|
+
private transport;
|
|
30
|
+
private sessionId;
|
|
31
|
+
private buffer;
|
|
32
|
+
private metadata;
|
|
33
|
+
private metadataSent;
|
|
34
|
+
private flushTimer;
|
|
35
|
+
private stopRecording;
|
|
36
|
+
private batchSize;
|
|
37
|
+
private constructor();
|
|
38
|
+
/** Initialize the Dozor recorder. Returns the singleton instance. */
|
|
39
|
+
static init(options: DozorOptions): Dozor;
|
|
40
|
+
/** Stop recording and flush remaining events. */
|
|
41
|
+
stop(): void;
|
|
42
|
+
private onEvent;
|
|
43
|
+
private flush;
|
|
44
|
+
private onBeforeUnload;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Dozor, type DozorOptions, type IngestPayload, type SessionMetadata };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {record}from'rrweb';function d(){return {url:location.href,referrer:document.referrer,userAgent:navigator.userAgent,screenWidth:screen.width,screenHeight:screen.height,language:navigator.language}}var l="dozor_session_id";function c(){try{let t=sessionStorage.getItem(l);if(t)return t}catch{}let n=crypto.randomUUID();try{sessionStorage.setItem(l,n);}catch{}return n}var a=class{constructor(t,e){this.endpoint=t,this.apiKey=e;}async send(t,e,s){let r={sessionId:t,events:e};s&&(r.metadata=s);let f=JSON.stringify(r);for(let o=0;o<3;o++){try{let h=await fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Dozor-Public-Key":this.apiKey},body:f,keepalive:!0});if(h.ok)return !0;if(h.status>=400&&h.status<500)return !1}catch{}o<2&&await u(1e3*2**o);}return false}sendKeepalive(t,e){if(e.length===0)return;let s={sessionId:t,events:e};try{fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Dozor-Public-Key":this.apiKey},body:JSON.stringify(s),keepalive:!0}).catch(()=>{});}catch{}}};function u(n){return new Promise(t=>setTimeout(t,n))}var v="https://dozor.kharko.dev/api/ingest",g=5e3,y=50,i=class i{constructor(t){this.buffer=[];this.metadataSent=false;this.flushTimer=null;this.stopRecording=null;let e=t.endpoint??v;this.batchSize=t.batchSize??y;let s=t.flushInterval??g;this.transport=new a(e,t.apiKey),this.sessionId=c(),this.metadata=d(),this.stopRecording=record({emit:r=>this.onEvent(r)})??null,this.flushTimer=setInterval(()=>this.flush(),s),addEventListener("beforeunload",()=>this.onBeforeUnload()),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush();});}static init(t){return i.instance||(i.instance=new i(t)),i.instance}stop(){this.stopRecording&&(this.stopRecording(),this.stopRecording=null),this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.flush(),i.instance=null;}onEvent(t){this.buffer.push(t),this.buffer.length>=this.batchSize&&this.flush();}flush(){if(this.buffer.length===0)return;let t=this.buffer;this.buffer=[];let e=this.metadataSent?void 0:this.metadata??void 0;e&&(this.metadataSent=true),this.transport.send(this.sessionId,t,e).catch(()=>{});}onBeforeUnload(){if(this.buffer.length===0)return;let t=this.buffer;this.buffer=[],this.transport.sendKeepalive(this.sessionId,t);}};i.instance=null;var p=i;
|
|
2
|
+
export{p as Dozor};//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/metadata.ts","../src/session.ts","../src/transport.ts","../src/recorder.ts"],"names":["collectMetadata","SESSION_KEY","getSessionId","existing","id","Transport","endpoint","apiKey","sessionId","events","metadata","payload","body","attempt","res","sleep","ms","resolve","DEFAULT_ENDPOINT","DEFAULT_FLUSH_INTERVAL","DEFAULT_BATCH_SIZE","_Dozor","options","flushInterval","record","event","Dozor"],"mappings":"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","file":"index.js","sourcesContent":["import type { SessionMetadata } from \"./types.js\";\n\n/** Collect session metadata from the browser environment. */\nexport function collectMetadata(): SessionMetadata {\n return {\n url: location.href,\n referrer: document.referrer,\n userAgent: navigator.userAgent,\n screenWidth: screen.width,\n screenHeight: screen.height,\n language: navigator.language,\n };\n}\n","const SESSION_KEY = \"dozor_session_id\";\n\n/** Get or create a session ID persisted in sessionStorage for SPA continuity. */\nexport function getSessionId(): string {\n try {\n const existing = sessionStorage.getItem(SESSION_KEY);\n if (existing) return existing;\n } catch {\n // sessionStorage unavailable (SSR, iframe sandbox, etc.)\n }\n\n const id = crypto.randomUUID();\n\n try {\n sessionStorage.setItem(SESSION_KEY, id);\n } catch {\n // best-effort persistence\n }\n\n return id;\n}\n","import type { eventWithTime } from \"rrweb\";\nimport type { IngestPayload, SessionMetadata } from \"./types.js\";\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\nexport class Transport {\n private endpoint: string;\n private apiKey: string;\n\n constructor(endpoint: string, apiKey: string) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n }\n\n /** Send a batch of events via fetch with retry. */\n async send(sessionId: string, events: eventWithTime[], metadata?: SessionMetadata): Promise<boolean> {\n const payload: IngestPayload = { sessionId, events };\n if (metadata) payload.metadata = metadata;\n\n const body = JSON.stringify(payload);\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const res = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Dozor-Public-Key\": this.apiKey,\n },\n body,\n keepalive: true,\n });\n\n if (res.ok) return true;\n\n // Don't retry client errors (400, 401, etc.)\n if (res.status >= 400 && res.status < 500) return false;\n } catch {\n // Network error — retry\n }\n\n if (attempt < MAX_RETRIES - 1) {\n await sleep(BASE_DELAY_MS * 2 ** attempt);\n }\n }\n\n return false;\n }\n\n /** Best-effort send via fetch with keepalive (for page unload). */\n sendKeepalive(sessionId: string, events: eventWithTime[]): void {\n if (events.length === 0) return;\n\n const payload: IngestPayload = { sessionId, events };\n\n try {\n fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Dozor-Public-Key\": this.apiKey,\n },\n body: JSON.stringify(payload),\n keepalive: true,\n }).catch(() => {});\n } catch {\n // best-effort, ignore failures during unload\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { record } from \"rrweb\";\nimport type { eventWithTime } from \"rrweb\";\nimport type { DozorOptions, SessionMetadata } from \"./types.js\";\nimport { collectMetadata } from \"./metadata.js\";\nimport { getSessionId } from \"./session.js\";\nimport { Transport } from \"./transport.js\";\n\nconst DEFAULT_ENDPOINT = \"https://dozor.kharko.dev/api/ingest\";\nconst DEFAULT_FLUSH_INTERVAL = 5_000;\nconst DEFAULT_BATCH_SIZE = 50;\n\nexport class Dozor {\n private static instance: Dozor | null = null;\n\n private transport: Transport;\n private sessionId: string;\n private buffer: eventWithTime[] = [];\n private metadata: SessionMetadata | null;\n private metadataSent = false;\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private stopRecording: (() => void) | null = null;\n private batchSize: number;\n\n private constructor(options: DozorOptions) {\n const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n const flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n\n this.transport = new Transport(endpoint, options.apiKey);\n this.sessionId = getSessionId();\n this.metadata = collectMetadata();\n\n this.stopRecording = record({\n emit: (event) => this.onEvent(event),\n }) ?? null;\n\n this.flushTimer = setInterval(() => this.flush(), flushInterval);\n\n addEventListener(\"beforeunload\", () => this.onBeforeUnload());\n addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") this.flush();\n });\n }\n\n /** Initialize the Dozor recorder. Returns the singleton instance. */\n static init(options: DozorOptions): Dozor {\n if (Dozor.instance) return Dozor.instance;\n Dozor.instance = new Dozor(options);\n return Dozor.instance;\n }\n\n /** Stop recording and flush remaining events. */\n stop(): void {\n if (this.stopRecording) {\n this.stopRecording();\n this.stopRecording = null;\n }\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n this.flush();\n Dozor.instance = null;\n }\n\n private onEvent(event: eventWithTime): void {\n this.buffer.push(event);\n\n if (this.buffer.length >= this.batchSize) {\n this.flush();\n }\n }\n\n private flush(): void {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer;\n this.buffer = [];\n\n const metadata = !this.metadataSent ? this.metadata ?? undefined : undefined;\n if (metadata) this.metadataSent = true;\n\n this.transport.send(this.sessionId, events, metadata).catch(() => {});\n }\n\n private onBeforeUnload(): void {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer;\n this.buffer = [];\n\n this.transport.sendKeepalive(this.sessionId, events);\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kharko/dozor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight session recording SDK for Kharko Dozor",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.cts",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"rrweb": "2.0.0-alpha.20"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"tsup": "^8.5.0",
|
|
34
|
+
"typescript": "^5"
|
|
35
|
+
}
|
|
36
|
+
}
|