@leadping/consent 0.0.0-stage → 1.0.1
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 +148 -2
- package/dist/browser/index.js +129 -0
- package/dist/browser/index.js.LEGAL.txt +17 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/leadping-consent.min.js +129 -0
- package/dist/browser/leadping-consent.min.js.LEGAL.txt +17 -0
- package/dist/browser/leadping-consent.min.js.map +7 -0
- package/dist/capture-rejected-error.d.ts +3 -0
- package/dist/capture-rejected-error.js +3 -0
- package/dist/capture.d.ts +34 -0
- package/dist/capture.js +184 -0
- package/dist/embed.d.ts +3 -0
- package/dist/embed.js +114 -0
- package/dist/event-buffer.d.ts +18 -0
- package/dist/event-buffer.js +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leadping-consent.min.js +129 -0
- package/dist/leadping-consent.min.js.LEGAL.txt +17 -0
- package/dist/leadping-consent.min.js.map +7 -0
- package/dist/transport.d.ts +10 -0
- package/dist/transport.js +51 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +1 -0
- package/package.json +35 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CaptureOptions, CaptureReference, Recipient } from './types.js';
|
|
2
|
+
/** One form recording per document, matching rrweb's document-level recorder. */
|
|
3
|
+
export declare class ConsentCapture {
|
|
4
|
+
private readonly options;
|
|
5
|
+
private static active?;
|
|
6
|
+
private readonly browser;
|
|
7
|
+
private readonly buffer;
|
|
8
|
+
private readonly transport;
|
|
9
|
+
private readonly pending;
|
|
10
|
+
private head;
|
|
11
|
+
private queuedBytes;
|
|
12
|
+
private sending?;
|
|
13
|
+
private stopRecording?;
|
|
14
|
+
private interval?;
|
|
15
|
+
private expiry?;
|
|
16
|
+
private failure?;
|
|
17
|
+
private stopped;
|
|
18
|
+
private finished?;
|
|
19
|
+
constructor(options: CaptureOptions);
|
|
20
|
+
/** Invoke before submitting your form; send the returned reference to your trusted backend with the lead. */
|
|
21
|
+
finish(recipient: Recipient): Promise<CaptureReference>;
|
|
22
|
+
/** Flush pending capture without stopping the recorder. Never silently discards a failed batch. */
|
|
23
|
+
flush(): Promise<void>;
|
|
24
|
+
dispose(): void;
|
|
25
|
+
private finishCore;
|
|
26
|
+
private readonly emit;
|
|
27
|
+
private seal;
|
|
28
|
+
private send;
|
|
29
|
+
private readonly consentChanged;
|
|
30
|
+
private readonly visibilityChanged;
|
|
31
|
+
private readonly backgroundFlush;
|
|
32
|
+
private fail;
|
|
33
|
+
private stop;
|
|
34
|
+
}
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { record } from 'rrweb';
|
|
2
|
+
import { EventBuffer } from './event-buffer.js';
|
|
3
|
+
import { CaptureTransport } from './transport.js';
|
|
4
|
+
/** One form recording per document, matching rrweb's document-level recorder. */
|
|
5
|
+
export class ConsentCapture {
|
|
6
|
+
options;
|
|
7
|
+
static active;
|
|
8
|
+
browser = {
|
|
9
|
+
userAgent: navigator.userAgent,
|
|
10
|
+
platform: navigator.platform,
|
|
11
|
+
language: navigator.language,
|
|
12
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
13
|
+
screenWidth: screen.width,
|
|
14
|
+
screenHeight: screen.height,
|
|
15
|
+
colorDepth: screen.colorDepth,
|
|
16
|
+
pixelRatio: window.devicePixelRatio,
|
|
17
|
+
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
18
|
+
maxTouchPoints: navigator.maxTouchPoints,
|
|
19
|
+
};
|
|
20
|
+
buffer = new EventBuffer();
|
|
21
|
+
transport;
|
|
22
|
+
pending = [];
|
|
23
|
+
head = 0;
|
|
24
|
+
queuedBytes = 0;
|
|
25
|
+
sending;
|
|
26
|
+
stopRecording;
|
|
27
|
+
interval;
|
|
28
|
+
expiry;
|
|
29
|
+
failure;
|
|
30
|
+
stopped = false;
|
|
31
|
+
finished;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.options = options;
|
|
34
|
+
if (ConsentCapture.active)
|
|
35
|
+
throw new Error('Only one consent recorder may run in a document.');
|
|
36
|
+
const url = new URL(options.apiUrl);
|
|
37
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash)
|
|
38
|
+
throw new Error('Capture API requires an HTTPS URL without credentials.');
|
|
39
|
+
if (!/^[0-9a-f-]{36}$/i.test(options.session.sessionId) || options.session.form.origin !== location.origin ||
|
|
40
|
+
!options.form.contains(options.checkbox) || !options.form.contains(options.disclosure) ||
|
|
41
|
+
options.checkbox.type !== 'checkbox' || options.checkbox.checked || options.checkbox.defaultChecked ||
|
|
42
|
+
options.disclosure.textContent !== options.session.form.disclosure)
|
|
43
|
+
throw new Error('Form does not match the approved disclosure and unchecked consent control.');
|
|
44
|
+
const remaining = Date.parse(options.session.expiresAt) - Date.now();
|
|
45
|
+
if (!Number.isFinite(remaining) || remaining <= 0 || remaining > 60 * 60 * 1000)
|
|
46
|
+
throw new Error('Invalid or expired capture session.');
|
|
47
|
+
this.transport = new CaptureTransport(url.href.replace(/\/$/, '') + '/consent/sessions/' + options.session.sessionId, options.session.uploadToken);
|
|
48
|
+
ConsentCapture.active = this;
|
|
49
|
+
try {
|
|
50
|
+
this.stopRecording = record({
|
|
51
|
+
emit: this.emit,
|
|
52
|
+
// Override rrweb defaults as well as Leadping selectors: preserve original values.
|
|
53
|
+
maskAllInputs: false,
|
|
54
|
+
maskInputOptions: {},
|
|
55
|
+
maskTextClass: /$^/,
|
|
56
|
+
blockClass: /$^/,
|
|
57
|
+
ignoreClass: '',
|
|
58
|
+
slimDOMOptions: {},
|
|
59
|
+
inlineStylesheet: true,
|
|
60
|
+
inlineImages: true,
|
|
61
|
+
recordCanvas: false,
|
|
62
|
+
recordCrossOriginIframes: false,
|
|
63
|
+
collectFonts: false,
|
|
64
|
+
sampling: { mousemove: 100, mouseInteraction: true, scroll: 150, input: 'all', media: 1000 },
|
|
65
|
+
errorHandler: () => { this.fail(new Error('The page could not be fully recorded.')); return true; },
|
|
66
|
+
});
|
|
67
|
+
if (!this.stopRecording || this.failure)
|
|
68
|
+
throw this.failure ?? new Error('Recorder failed to start.');
|
|
69
|
+
options.checkbox.addEventListener('change', this.consentChanged);
|
|
70
|
+
document.addEventListener('visibilitychange', this.visibilityChanged);
|
|
71
|
+
this.interval = setInterval(this.backgroundFlush, 3000);
|
|
72
|
+
this.expiry = setTimeout(() => this.fail(new Error('Capture session expired.')), remaining);
|
|
73
|
+
record.addCustomEvent('leadping.disclosure', { formId: options.session.form.id });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.stop();
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Invoke before submitting your form; send the returned reference to your trusted backend with the lead. */
|
|
81
|
+
finish(recipient) {
|
|
82
|
+
return this.finished ??= this.finishCore(recipient);
|
|
83
|
+
}
|
|
84
|
+
/** Flush pending capture without stopping the recorder. Never silently discards a failed batch. */
|
|
85
|
+
async flush() {
|
|
86
|
+
if (this.failure)
|
|
87
|
+
throw this.failure;
|
|
88
|
+
this.seal();
|
|
89
|
+
while (this.head < this.pending.length || this.sending) {
|
|
90
|
+
this.sending ??= this.send();
|
|
91
|
+
await this.sending;
|
|
92
|
+
}
|
|
93
|
+
if (this.failure)
|
|
94
|
+
throw this.failure;
|
|
95
|
+
}
|
|
96
|
+
dispose() { this.stop(); this.transport.dispose(); }
|
|
97
|
+
async finishCore(recipient) {
|
|
98
|
+
if (this.stopped || this.failure)
|
|
99
|
+
throw this.failure ?? new Error('Recorder is stopped.');
|
|
100
|
+
const observedDisclosure = this.options.disclosure.textContent ?? '';
|
|
101
|
+
if (observedDisclosure !== this.options.session.form.disclosure)
|
|
102
|
+
throw new Error('The disclosure changed during capture. Start a new approved form session.');
|
|
103
|
+
const accepted = this.options.checkbox.checked;
|
|
104
|
+
record.addCustomEvent('leadping.submit', { accepted });
|
|
105
|
+
this.stop();
|
|
106
|
+
await this.flush();
|
|
107
|
+
await this.transport.put('/submission', JSON.stringify({
|
|
108
|
+
lastBatchNumber: this.buffer.lastBatchNumber, lastEventNumber: this.buffer.lastEventNumber,
|
|
109
|
+
accepted, observedDisclosure, recipient, browser: this.browser,
|
|
110
|
+
}));
|
|
111
|
+
return { sessionId: this.options.session.sessionId, uploadToken: this.options.session.uploadToken };
|
|
112
|
+
}
|
|
113
|
+
emit = (event) => {
|
|
114
|
+
if (this.stopped || this.failure)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
117
|
+
const serialized = JSON.stringify(event);
|
|
118
|
+
if (!this.buffer.canAppend(serialized))
|
|
119
|
+
this.seal();
|
|
120
|
+
this.buffer.append(serialized);
|
|
121
|
+
if (this.buffer.byteLength >= 64 * 1024)
|
|
122
|
+
this.backgroundFlush();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
this.fail(new Error('Recording exceeded its memory or event size limit.'));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
seal() {
|
|
129
|
+
const batch = this.buffer.seal();
|
|
130
|
+
if (!batch)
|
|
131
|
+
return;
|
|
132
|
+
// UTF-16 string budget including queued/retrying batches. One in-flight compressed copy is additional.
|
|
133
|
+
if (this.queuedBytes + batch.body.length * 2 > 2 * 1024 * 1024)
|
|
134
|
+
throw new Error('Recording upload backlog exceeded 2 MiB.');
|
|
135
|
+
this.pending.push(batch);
|
|
136
|
+
this.queuedBytes += batch.body.length * 2;
|
|
137
|
+
}
|
|
138
|
+
async send() {
|
|
139
|
+
// Defer so this.sending is assigned before this function can finish.
|
|
140
|
+
await Promise.resolve();
|
|
141
|
+
try {
|
|
142
|
+
while (this.head < this.pending.length) {
|
|
143
|
+
const batch = this.pending[this.head];
|
|
144
|
+
await this.transport.put('/batches/' + batch.number, batch.body);
|
|
145
|
+
this.queuedBytes -= batch.body.length * 2;
|
|
146
|
+
this.pending[this.head] = undefined;
|
|
147
|
+
this.head++;
|
|
148
|
+
if (this.head === this.pending.length) {
|
|
149
|
+
this.pending.length = 0;
|
|
150
|
+
this.head = 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.fail(error instanceof Error ? error : new Error('Capture upload failed.'));
|
|
156
|
+
throw this.failure;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.sending = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
consentChanged = () => record.addCustomEvent('leadping.consent', { accepted: this.options.checkbox.checked });
|
|
163
|
+
visibilityChanged = () => { if (document.visibilityState === 'hidden')
|
|
164
|
+
this.backgroundFlush(); };
|
|
165
|
+
backgroundFlush = () => { void this.flush().catch(error => this.fail(error)); };
|
|
166
|
+
fail(error) {
|
|
167
|
+
if (this.failure)
|
|
168
|
+
return;
|
|
169
|
+
this.failure = error;
|
|
170
|
+
this.stop();
|
|
171
|
+
this.options.onError?.(error);
|
|
172
|
+
}
|
|
173
|
+
stop() {
|
|
174
|
+
this.stopped = true;
|
|
175
|
+
this.stopRecording?.();
|
|
176
|
+
this.stopRecording = undefined;
|
|
177
|
+
clearInterval(this.interval);
|
|
178
|
+
clearTimeout(this.expiry);
|
|
179
|
+
this.options.checkbox.removeEventListener('change', this.consentChanged);
|
|
180
|
+
document.removeEventListener('visibilitychange', this.visibilityChanged);
|
|
181
|
+
if (ConsentCapture.active === this)
|
|
182
|
+
ConsentCapture.active = undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
package/dist/embed.d.ts
ADDED
package/dist/embed.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ConsentCapture } from './capture.js';
|
|
2
|
+
export { ConsentCapture } from './capture.js';
|
|
3
|
+
/** Attach the Leadping-hosted capture and submission flow to a form. */
|
|
4
|
+
export async function attach(script) {
|
|
5
|
+
const domainId = script.dataset.domainId;
|
|
6
|
+
if (!domainId)
|
|
7
|
+
throw new Error('data-domain-id is required.');
|
|
8
|
+
const form = document.querySelector(script.dataset.form ?? '#lead-form');
|
|
9
|
+
if (!(form instanceof HTMLFormElement))
|
|
10
|
+
throw new Error('Leadping form was not found.');
|
|
11
|
+
if (form.dataset.leadpingAttached)
|
|
12
|
+
throw new Error('Leadping is already attached to this form.');
|
|
13
|
+
form.dataset.leadpingAttached = 'true';
|
|
14
|
+
const disclosure = form.querySelector('[data-leadping-disclosure]');
|
|
15
|
+
const checkbox = form.querySelector('[data-leadping-consent]');
|
|
16
|
+
const status = document.createElement('p');
|
|
17
|
+
status.setAttribute('role', 'status');
|
|
18
|
+
form.append(status);
|
|
19
|
+
let capture;
|
|
20
|
+
let ready = false;
|
|
21
|
+
let submitted = false;
|
|
22
|
+
const fail = (error) => {
|
|
23
|
+
ready = false;
|
|
24
|
+
status.textContent = 'Unable to complete consent capture. Please reload to start again.';
|
|
25
|
+
form.dispatchEvent(new CustomEvent('leadping:error', { detail: error }));
|
|
26
|
+
};
|
|
27
|
+
const origin = new URL(script.dataset.apiUrl ?? 'https://consent.leadping.ai');
|
|
28
|
+
if (origin.protocol !== 'https:' || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== '/')
|
|
29
|
+
throw new Error('Leadping requires an HTTPS API origin.');
|
|
30
|
+
const portal = new URL(script.dataset.portalUrl ?? (origin.hostname === 'consent-local.leadping.ai'
|
|
31
|
+
? 'https://certificate-local.leadping.ai' : 'https://certificate.leadping.ai'));
|
|
32
|
+
if (portal.protocol !== 'https:' || portal.username || portal.password || portal.search || portal.hash || portal.pathname !== '/')
|
|
33
|
+
throw new Error('Leadping requires an HTTPS portal origin.');
|
|
34
|
+
const endpoint = `${origin.origin}/api/consent`;
|
|
35
|
+
const post = async (path, body) => {
|
|
36
|
+
const response = await fetch(endpoint + path, {
|
|
37
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
credentials: 'omit', redirect: 'error', body: JSON.stringify(body),
|
|
39
|
+
signal: AbortSignal.timeout(30000),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok)
|
|
42
|
+
throw new Error(`Leadping request failed (${response.status}).`);
|
|
43
|
+
return response;
|
|
44
|
+
};
|
|
45
|
+
form.addEventListener('submit', async (event) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
if (!ready || submitted || !capture || !form.reportValidity())
|
|
48
|
+
return;
|
|
49
|
+
if (!(checkbox instanceof HTMLInputElement) || !checkbox.checked) {
|
|
50
|
+
status.textContent = 'Please check the consent box before submitting.';
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
submitted = true;
|
|
54
|
+
ready = false;
|
|
55
|
+
status.textContent = 'Submitting…';
|
|
56
|
+
const values = new FormData(form);
|
|
57
|
+
const recipient = {};
|
|
58
|
+
for (const key of ['firstName', 'lastName', 'email', 'phone']) {
|
|
59
|
+
const value = values.get(key);
|
|
60
|
+
if (typeof value === 'string' && value)
|
|
61
|
+
recipient[key] = value;
|
|
62
|
+
}
|
|
63
|
+
let issuingCertificate = false;
|
|
64
|
+
try {
|
|
65
|
+
const consentCapture = await capture.finish(recipient);
|
|
66
|
+
issuingCertificate = true;
|
|
67
|
+
const certificate = await (await post('/certificates', consentCapture)).json();
|
|
68
|
+
if (!/^[0-9a-f-]{36}$/i.test(certificate.certificateId))
|
|
69
|
+
throw new Error('Invalid certificate response.');
|
|
70
|
+
const certificateUrl = `${portal.origin}/${certificate.certificateId}`;
|
|
71
|
+
status.textContent = `Certificate received: ${certificate.certificateId}. `;
|
|
72
|
+
const link = document.createElement('a');
|
|
73
|
+
link.href = certificateUrl;
|
|
74
|
+
link.textContent = 'View certificate and replay';
|
|
75
|
+
link.target = '_blank';
|
|
76
|
+
link.rel = 'noopener noreferrer';
|
|
77
|
+
status.append(link);
|
|
78
|
+
form.dispatchEvent(new CustomEvent('leadping:success', { detail: { certificateId: certificate.certificateId, certificateUrl } }));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
fail(error);
|
|
82
|
+
if (issuingCertificate)
|
|
83
|
+
status.textContent = 'Certificate issuance could not be confirmed. Please check before starting another recording.';
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
capture.dispose();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
status.textContent = 'Loading consent…';
|
|
90
|
+
try {
|
|
91
|
+
if (!(disclosure instanceof HTMLElement) || !(checkbox instanceof HTMLInputElement))
|
|
92
|
+
throw new Error('Add data-leadping-disclosure and data-leadping-consent inside the form.');
|
|
93
|
+
const session = await (await post(`/domains/${encodeURIComponent(domainId)}/sessions`, { pageUrl: location.href, disclosure: disclosure.textContent ?? '' })).json();
|
|
94
|
+
checkbox.checked = false;
|
|
95
|
+
capture = new ConsentCapture({ apiUrl: origin.origin + '/api', session, form, disclosure, checkbox, onError: fail });
|
|
96
|
+
window.addEventListener('pagehide', () => { ready = false; capture?.dispose(); }, { once: true });
|
|
97
|
+
ready = true;
|
|
98
|
+
status.textContent = '';
|
|
99
|
+
form.dispatchEvent(new CustomEvent('leadping:ready'));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
capture?.dispose();
|
|
103
|
+
fail(error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const script = document.currentScript;
|
|
108
|
+
if (script instanceof HTMLScriptElement && script.dataset.domainId) {
|
|
109
|
+
const start = () => { void attach(script).catch(error => console.error('Leadping consent initialization failed:', error)); };
|
|
110
|
+
if (document.readyState === 'loading')
|
|
111
|
+
document.addEventListener('DOMContentLoaded', start, { once: true });
|
|
112
|
+
else
|
|
113
|
+
start();
|
|
114
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SealedBatch } from './types.js';
|
|
2
|
+
/** UTF-8 size without allocating an encoded copy of each event. */
|
|
3
|
+
export declare function utf8Length(value: string): number;
|
|
4
|
+
/** Bounded append buffer. Serialize once, clear references after each batch, never shift event arrays. */
|
|
5
|
+
export declare class EventBuffer {
|
|
6
|
+
private readonly events;
|
|
7
|
+
private bytes;
|
|
8
|
+
private nextEvent;
|
|
9
|
+
private nextBatch;
|
|
10
|
+
readonly maxBytes: number;
|
|
11
|
+
get byteLength(): number;
|
|
12
|
+
get count(): number;
|
|
13
|
+
get lastEventNumber(): number;
|
|
14
|
+
get lastBatchNumber(): number;
|
|
15
|
+
canAppend(serialized: string): boolean;
|
|
16
|
+
append(serialized: string): void;
|
|
17
|
+
seal(): SealedBatch | undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** UTF-8 size without allocating an encoded copy of each event. */
|
|
2
|
+
export function utf8Length(value) {
|
|
3
|
+
let bytes = value.length;
|
|
4
|
+
for (let i = 0; i < value.length; i++) {
|
|
5
|
+
const c = value.charCodeAt(i);
|
|
6
|
+
if (c < 0x80)
|
|
7
|
+
continue;
|
|
8
|
+
if (c < 0x800)
|
|
9
|
+
bytes++;
|
|
10
|
+
else if (c >= 0xd800 && c <= 0xdbff && i + 1 < value.length &&
|
|
11
|
+
value.charCodeAt(i + 1) >= 0xdc00 && value.charCodeAt(i + 1) <= 0xdfff) {
|
|
12
|
+
bytes += 2;
|
|
13
|
+
i++;
|
|
14
|
+
}
|
|
15
|
+
else
|
|
16
|
+
bytes += 2;
|
|
17
|
+
}
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
/** Bounded append buffer. Serialize once, clear references after each batch, never shift event arrays. */
|
|
21
|
+
export class EventBuffer {
|
|
22
|
+
events = [];
|
|
23
|
+
bytes = 0;
|
|
24
|
+
nextEvent = 0;
|
|
25
|
+
nextBatch = 0;
|
|
26
|
+
maxBytes = 256 * 1024 - 128;
|
|
27
|
+
get byteLength() { return this.bytes; }
|
|
28
|
+
get count() { return this.events.length; }
|
|
29
|
+
get lastEventNumber() { return this.nextEvent - 1; }
|
|
30
|
+
get lastBatchNumber() { return this.nextBatch - 1; }
|
|
31
|
+
canAppend(serialized) {
|
|
32
|
+
return this.events.length < 4096 && this.bytes + utf8Length(serialized) + 1 <= this.maxBytes;
|
|
33
|
+
}
|
|
34
|
+
append(serialized) {
|
|
35
|
+
const size = utf8Length(serialized) + 1;
|
|
36
|
+
if (size > this.maxBytes || this.bytes + size > this.maxBytes || this.events.length >= 4096)
|
|
37
|
+
throw new Error('Recording event exceeds the capture buffer limit.');
|
|
38
|
+
this.events.push(serialized);
|
|
39
|
+
this.bytes += size;
|
|
40
|
+
this.nextEvent++;
|
|
41
|
+
}
|
|
42
|
+
seal() {
|
|
43
|
+
if (this.events.length === 0)
|
|
44
|
+
return;
|
|
45
|
+
if (this.nextBatch >= 256)
|
|
46
|
+
throw new Error('Recording exceeded the session batch limit.');
|
|
47
|
+
const first = this.nextEvent - this.events.length;
|
|
48
|
+
const body = `{"firstEventNumber":${first},"events":[${this.events.join(',')}]}`;
|
|
49
|
+
this.events.length = 0;
|
|
50
|
+
this.bytes = 0;
|
|
51
|
+
return { number: this.nextBatch++, firstEventNumber: first, lastEventNumber: this.nextEvent - 1, body };
|
|
52
|
+
}
|
|
53
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ConsentCapture } from './capture.js';
|