@offscript/sdk 0.0.0-stage → 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/README.md +133 -2
- package/dist/adapters/nuxt.cjs +16 -0
- package/dist/adapters/nuxt.cjs.map +1 -0
- package/dist/adapters/nuxt.d.ts +10 -0
- package/dist/adapters/nuxt.js +14 -0
- package/dist/adapters/nuxt.js.map +1 -0
- package/dist/adapters/vanilla.cjs +18 -0
- package/dist/adapters/vanilla.cjs.map +1 -0
- package/dist/adapters/vanilla.d.ts +6 -0
- package/dist/adapters/vanilla.js +5 -0
- package/dist/adapters/vanilla.js.map +1 -0
- package/dist/adapters/vue.cjs +24 -0
- package/dist/adapters/vue.cjs.map +1 -0
- package/dist/adapters/vue.d.ts +18 -0
- package/dist/adapters/vue.js +21 -0
- package/dist/adapters/vue.js.map +1 -0
- package/dist/chunk-3NX2AGXM.cjs +2671 -0
- package/dist/chunk-3NX2AGXM.cjs.map +1 -0
- package/dist/chunk-3OSCH7J2.js +2658 -0
- package/dist/chunk-3OSCH7J2.js.map +1 -0
- package/dist/chunk-B6GO5FE5.cjs +442 -0
- package/dist/chunk-B6GO5FE5.cjs.map +1 -0
- package/dist/chunk-HAPNSHFG.cjs +20 -0
- package/dist/chunk-HAPNSHFG.cjs.map +1 -0
- package/dist/chunk-LFXHB4PN.js +426 -0
- package/dist/chunk-LFXHB4PN.js.map +1 -0
- package/dist/chunk-MBYNQSKA.js +17 -0
- package/dist/chunk-MBYNQSKA.js.map +1 -0
- package/dist/core/ApiClient.d.ts +24 -0
- package/dist/core/LifecycleManager.d.ts +26 -0
- package/dist/core/OffscriptSDK.d.ts +98 -0
- package/dist/core/SessionManager.d.ts +44 -0
- package/dist/core/config.d.ts +2 -0
- package/dist/index.cjs +98 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/playwright/ActionNormalizer.d.ts +37 -0
- package/dist/playwright/PlaywrightDataContract.d.ts +88 -0
- package/dist/playwright/SelectorGenerator.d.ts +26 -0
- package/dist/playwright/index.d.ts +3 -0
- package/dist/playwright.cjs +56 -0
- package/dist/playwright.cjs.map +1 -0
- package/dist/playwright.js +3 -0
- package/dist/playwright.js.map +1 -0
- package/dist/privacy/DataSanitizer.d.ts +32 -0
- package/dist/privacy/PrivacyConfig.d.ts +4 -0
- package/dist/recorders/ActionRecorder.d.ts +46 -0
- package/dist/recorders/ContextRecorder.d.ts +23 -0
- package/dist/recorders/ErrorRecorder.d.ts +20 -0
- package/dist/recorders/HTTPRecorder.d.ts +28 -0
- package/dist/recorders/IframeRecorder.d.ts +19 -0
- package/dist/recorders/RRWebRecorder.d.ts +29 -0
- package/dist/recorders/types.d.ts +25 -0
- package/dist/transport/EventQueue.d.ts +28 -0
- package/dist/transport/EventTransport.d.ts +84 -0
- package/dist/transport/PersistentBuffer.d.ts +60 -0
- package/dist/transport/RetryManager.d.ts +9 -0
- package/dist/types/config.d.ts +115 -0
- package/dist/types/events.d.ts +185 -0
- package/dist/types/session.d.ts +77 -0
- package/dist/utils/errors.d.ts +41 -0
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/misc.d.ts +20 -0
- package/docs/api-contract.md +56 -0
- package/docs/architecture.md +38 -0
- package/docs/event-contract.md +26 -0
- package/docs/playwright-contract.md +39 -0
- package/package.json +78 -3
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EventSink, type Recorder } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Detecta iframes (ex.: seats.io) e registra metadados observáveis.
|
|
4
|
+
* Nunca tenta acessar o DOM interno do iframe.
|
|
5
|
+
*/
|
|
6
|
+
export declare class IframeRecorder implements Recorder {
|
|
7
|
+
private readonly sink;
|
|
8
|
+
readonly name = "iframes";
|
|
9
|
+
private d;
|
|
10
|
+
private known;
|
|
11
|
+
private counter;
|
|
12
|
+
private mo?;
|
|
13
|
+
constructor(sink: EventSink);
|
|
14
|
+
start(): void;
|
|
15
|
+
private scan;
|
|
16
|
+
private add;
|
|
17
|
+
private event;
|
|
18
|
+
stop(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type EventSink, type Recorder } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Captura visual (DOM + mutações + interações) usando rrweb. Carregado sob demanda.
|
|
4
|
+
* Não produz ações semânticas: isso é responsabilidade do ActionRecorder.
|
|
5
|
+
*/
|
|
6
|
+
export declare class RRWebRecorder implements Recorder {
|
|
7
|
+
private readonly sink;
|
|
8
|
+
readonly name = "rrweb";
|
|
9
|
+
private stopFn?;
|
|
10
|
+
private mod?;
|
|
11
|
+
private paused;
|
|
12
|
+
private stopped;
|
|
13
|
+
needsSnapshot: boolean;
|
|
14
|
+
private snapshotsTaken;
|
|
15
|
+
constructor(sink: EventSink);
|
|
16
|
+
start(): Promise<void>;
|
|
17
|
+
private onEvent;
|
|
18
|
+
/** Sanitiza URLs em atributos e padrões sensíveis em textos ANTES de persistir/enviar. */
|
|
19
|
+
private sanitizeEvent;
|
|
20
|
+
/** Chamado pelo núcleo quando há folga novamente após descartes. */
|
|
21
|
+
recoverIfNeeded(): void;
|
|
22
|
+
takeFullSnapshot(): void;
|
|
23
|
+
addCustomEvent(tag: string, payload: unknown): void;
|
|
24
|
+
get snapshots(): number;
|
|
25
|
+
pause(): void;
|
|
26
|
+
/** Ao retomar, as mutações perdidas durante a pausa exigem um snapshot completo. */
|
|
27
|
+
resume(): void;
|
|
28
|
+
stop(): void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Priority, ResolvedConfig } from "../types/config";
|
|
2
|
+
import type { EventKind, OffscriptEvent } from "../types/events";
|
|
3
|
+
import type { DataSanitizer } from "../privacy/DataSanitizer";
|
|
4
|
+
export interface EmitOptions {
|
|
5
|
+
priority?: Priority;
|
|
6
|
+
/** Momento em que o fato ocorreu, se diferente de agora. */
|
|
7
|
+
at?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Contrato entre os recorders e o núcleo. Recorders nunca falam com o transporte diretamente. */
|
|
10
|
+
export interface EventSink {
|
|
11
|
+
readonly config: ResolvedConfig;
|
|
12
|
+
readonly sanitizer: DataSanitizer;
|
|
13
|
+
emit<T>(kind: EventKind, data: T, opts?: EmitOptions): OffscriptEvent<T> | null;
|
|
14
|
+
}
|
|
15
|
+
export interface Recorder {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
start(): void | Promise<void>;
|
|
18
|
+
stop(): void;
|
|
19
|
+
pause?(): void;
|
|
20
|
+
resume?(): void | Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/** Executa um trecho sem nunca propagar erro para a aplicação hospedeira. */
|
|
23
|
+
export declare function guard(sink: {
|
|
24
|
+
emit: EventSink["emit"];
|
|
25
|
+
} | null, name: string, fn: () => void): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OffscriptEvent } from "../types/events";
|
|
2
|
+
import type { Priority } from "../types/config";
|
|
3
|
+
/**
|
|
4
|
+
* Fila de staging em memória + controle de backpressure.
|
|
5
|
+
* Eventos "critical" (snapshots rrweb, lifecycle, ações) nunca são descartados
|
|
6
|
+
* antes dos "low" (mousemove, scroll, console) e têm um teto 2x maior.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EventQueue {
|
|
9
|
+
private readonly maxEvents;
|
|
10
|
+
private readonly maxBytes;
|
|
11
|
+
private staged;
|
|
12
|
+
private pendingCount;
|
|
13
|
+
private pendingBytes;
|
|
14
|
+
dropped: number;
|
|
15
|
+
constructor(maxEvents: number, maxBytes: number);
|
|
16
|
+
canAccept(priority: Priority): boolean;
|
|
17
|
+
noteDropped(): void;
|
|
18
|
+
push(e: OffscriptEvent, size: number): void;
|
|
19
|
+
/** Devolve eventos ao staging sem recontar. */
|
|
20
|
+
restore(events: OffscriptEvent[]): void;
|
|
21
|
+
takeStaged(): OffscriptEvent[];
|
|
22
|
+
peekStaged(): readonly OffscriptEvent[];
|
|
23
|
+
get stagedCount(): number;
|
|
24
|
+
acked(count: number, bytes: number): void;
|
|
25
|
+
reset(count: number, bytes: number): void;
|
|
26
|
+
get pending(): number;
|
|
27
|
+
get bytes(): number;
|
|
28
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type OffscriptEvent } from "../types/events";
|
|
2
|
+
import type { Priority, TransportConfig } from "../types/config";
|
|
3
|
+
import { ApiClient, nativeFetch } from "../core/ApiClient";
|
|
4
|
+
import { AuthError, Emitter, SessionExpiredError } from "../utils/errors";
|
|
5
|
+
import type { EventBuffer } from "./PersistentBuffer";
|
|
6
|
+
export interface TransportIdentity {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
executionId: string;
|
|
9
|
+
tabId: string;
|
|
10
|
+
}
|
|
11
|
+
export type TransportEvents = {
|
|
12
|
+
flushed: {
|
|
13
|
+
events: number;
|
|
14
|
+
batchId: string;
|
|
15
|
+
duplicate: boolean;
|
|
16
|
+
};
|
|
17
|
+
failed: {
|
|
18
|
+
error: unknown;
|
|
19
|
+
willRetryInMs?: number;
|
|
20
|
+
};
|
|
21
|
+
fatal: {
|
|
22
|
+
error: AuthError | SessionExpiredError;
|
|
23
|
+
};
|
|
24
|
+
overflow: {
|
|
25
|
+
dropped: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export interface DrainOptions {
|
|
29
|
+
inlineRetries: number;
|
|
30
|
+
deadline?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class EventTransport extends Emitter<TransportEvents> {
|
|
33
|
+
private readonly cfg;
|
|
34
|
+
private readonly api;
|
|
35
|
+
private readonly buffer;
|
|
36
|
+
private queue;
|
|
37
|
+
private retry;
|
|
38
|
+
private identity;
|
|
39
|
+
private commitTimer?;
|
|
40
|
+
private flushTimer?;
|
|
41
|
+
private retryTimer?;
|
|
42
|
+
private running;
|
|
43
|
+
private again;
|
|
44
|
+
private failures;
|
|
45
|
+
private stopped;
|
|
46
|
+
private fatal;
|
|
47
|
+
private online;
|
|
48
|
+
private lastDroppedReported;
|
|
49
|
+
constructor(cfg: TransportConfig, api: ApiClient, buffer: EventBuffer);
|
|
50
|
+
get bufferKind(): string;
|
|
51
|
+
/** Inicia o transporte para uma sessão/aba e recupera o que ficou pendente no buffer. */
|
|
52
|
+
start(identity: TransportIdentity): Promise<void>;
|
|
53
|
+
setOnline(online: boolean): void;
|
|
54
|
+
canAccept(priority: Priority): boolean;
|
|
55
|
+
noteDropped(): void;
|
|
56
|
+
get pending(): number;
|
|
57
|
+
enqueue(event: OffscriptEvent): void;
|
|
58
|
+
private commit;
|
|
59
|
+
private scheduleFlush;
|
|
60
|
+
/** Flush de melhor esforço. Retorna true se tudo foi confirmado pelo backend. */
|
|
61
|
+
flush(opts?: DrainOptions): Promise<boolean>;
|
|
62
|
+
/** Flush final com retries inline e prazo máximo. */
|
|
63
|
+
flushFinal(maxWaitMs?: number): Promise<{
|
|
64
|
+
ok: boolean;
|
|
65
|
+
pending: number;
|
|
66
|
+
}>;
|
|
67
|
+
private drain;
|
|
68
|
+
private onFailure;
|
|
69
|
+
private sendWithRetry;
|
|
70
|
+
private buildBatch;
|
|
71
|
+
/**
|
|
72
|
+
* Chamado em pagehide/beforeunload. Persiste o que está em staging (best effort, assíncrono) e
|
|
73
|
+
* tenta enviar o final da fila com fetch keepalive (limite ~64KB). Sem ACK: os eventos continuam
|
|
74
|
+
* no IndexedDB e serão reenviados (de forma idempotente) na próxima página.
|
|
75
|
+
* Não há garantia de entrega se o navegador for encerrado abruptamente.
|
|
76
|
+
*/
|
|
77
|
+
flushOnUnload(): void;
|
|
78
|
+
/** Para timers; eventos não confirmados permanecem no buffer. */
|
|
79
|
+
stop(): void;
|
|
80
|
+
clearSession(sessionId: string): Promise<void>;
|
|
81
|
+
purgeExpired(retentionMs: number): Promise<void>;
|
|
82
|
+
close(): void;
|
|
83
|
+
}
|
|
84
|
+
export { nativeFetch };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { OffscriptEvent } from "../types/events";
|
|
2
|
+
export interface StoredEvent {
|
|
3
|
+
key: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
tabId: string;
|
|
6
|
+
sequence: number;
|
|
7
|
+
storedAt: number;
|
|
8
|
+
size: number;
|
|
9
|
+
event: OffscriptEvent;
|
|
10
|
+
}
|
|
11
|
+
export interface PeekResult {
|
|
12
|
+
items: StoredEvent[];
|
|
13
|
+
bytes: number;
|
|
14
|
+
}
|
|
15
|
+
export interface EventBuffer {
|
|
16
|
+
readonly kind: "indexeddb" | "memory";
|
|
17
|
+
put(events: OffscriptEvent[]): Promise<void>;
|
|
18
|
+
peek(sessionId: string, tabId: string, maxEvents: number, maxBytes: number): Promise<PeekResult>;
|
|
19
|
+
remove(keys: string[]): Promise<void>;
|
|
20
|
+
stats(sessionId?: string): Promise<{
|
|
21
|
+
count: number;
|
|
22
|
+
bytes: number;
|
|
23
|
+
}>;
|
|
24
|
+
purgeOlderThan(ts: number): Promise<number>;
|
|
25
|
+
clearSession(sessionId: string): Promise<void>;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare const eventKey: (sessionId: string, tabId: string, seq: number) => string;
|
|
29
|
+
export declare class MemoryBuffer implements EventBuffer {
|
|
30
|
+
readonly kind: "memory";
|
|
31
|
+
private map;
|
|
32
|
+
put(events: OffscriptEvent[]): Promise<void>;
|
|
33
|
+
peek(sessionId: string, tabId: string, maxEvents: number, maxBytes: number): Promise<PeekResult>;
|
|
34
|
+
remove(keys: string[]): Promise<void>;
|
|
35
|
+
stats(sessionId?: string): Promise<{
|
|
36
|
+
count: number;
|
|
37
|
+
bytes: number;
|
|
38
|
+
}>;
|
|
39
|
+
purgeOlderThan(ts: number): Promise<number>;
|
|
40
|
+
clearSession(sessionId: string): Promise<void>;
|
|
41
|
+
close(): void;
|
|
42
|
+
}
|
|
43
|
+
/** Persistência temporária em IndexedDB. Eventos só saem daqui após o ACK do backend. */
|
|
44
|
+
export declare class IndexedDBBuffer implements EventBuffer {
|
|
45
|
+
private db;
|
|
46
|
+
readonly kind: "indexeddb";
|
|
47
|
+
private constructor();
|
|
48
|
+
static open(name: string): Promise<IndexedDBBuffer>;
|
|
49
|
+
put(events: OffscriptEvent[]): Promise<void>;
|
|
50
|
+
peek(sessionId: string, tabId: string, maxEvents: number, maxBytes: number): Promise<PeekResult>;
|
|
51
|
+
remove(keys: string[]): Promise<void>;
|
|
52
|
+
stats(sessionId?: string): Promise<{
|
|
53
|
+
count: number;
|
|
54
|
+
bytes: number;
|
|
55
|
+
}>;
|
|
56
|
+
purgeOlderThan(ts: number): Promise<number>;
|
|
57
|
+
clearSession(sessionId: string): Promise<void>;
|
|
58
|
+
close(): void;
|
|
59
|
+
}
|
|
60
|
+
export declare function openBuffer(name: string, persist: boolean): Promise<EventBuffer>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Exponential backoff com full jitter. */
|
|
2
|
+
export declare class RetryManager {
|
|
3
|
+
private readonly baseMs;
|
|
4
|
+
private readonly maxMs;
|
|
5
|
+
private readonly random;
|
|
6
|
+
constructor(baseMs: number, maxMs: number, random?: () => number);
|
|
7
|
+
delay(attempt: number): number;
|
|
8
|
+
sleep(ms: number): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type Priority = "critical" | "normal" | "low";
|
|
2
|
+
export interface RecordingConfig {
|
|
3
|
+
/** Gravação visual (rrweb). */
|
|
4
|
+
rrweb: boolean;
|
|
5
|
+
/** Ações semânticas (clique, input, teclado...). */
|
|
6
|
+
actions: boolean;
|
|
7
|
+
/** Instrumentação de fetch/XMLHttpRequest. */
|
|
8
|
+
http: boolean;
|
|
9
|
+
/** console.* */
|
|
10
|
+
console: boolean;
|
|
11
|
+
/** window.onerror / unhandledrejection */
|
|
12
|
+
errors: boolean;
|
|
13
|
+
/** Metadados de contexto (navegador, viewport...). */
|
|
14
|
+
context: boolean;
|
|
15
|
+
/** Detecção de iframes de terceiros (ex.: seats.io). */
|
|
16
|
+
iframes: boolean;
|
|
17
|
+
/** Eventos de mousemove no Action Recorder (rrweb já captura o movimento visual). */
|
|
18
|
+
mouseMove: boolean;
|
|
19
|
+
/** Níveis de console capturados. */
|
|
20
|
+
consoleLevels: Array<"log" | "info" | "warn" | "error">;
|
|
21
|
+
/** Intervalo mínimo entre amostras de mousemove/scroll (ms). */
|
|
22
|
+
throttleMs: number;
|
|
23
|
+
/** Snapshot completo periódico do rrweb (ms). 0 = desligado. */
|
|
24
|
+
rrwebCheckoutEveryMs: number;
|
|
25
|
+
/** Captura de canvas pelo rrweb (custoso). */
|
|
26
|
+
rrwebRecordCanvas: boolean;
|
|
27
|
+
/** Atributos considerados identificadores de teste, em ordem de prioridade. */
|
|
28
|
+
testIdAttributes: string[];
|
|
29
|
+
/** Atributos estáveis adicionais usados na geração de seletores. */
|
|
30
|
+
stableAttributes: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface PrivacyConfig {
|
|
33
|
+
/** Mascara valores de <input>/<textarea>/<select> no replay. Padrão: true. */
|
|
34
|
+
maskSensitiveInputs: boolean;
|
|
35
|
+
/** Mascara todo texto da página no replay. Padrão: false. */
|
|
36
|
+
maskAllText: boolean;
|
|
37
|
+
captureRequestBodies: boolean;
|
|
38
|
+
captureResponseBodies: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Valores digitados só são gravados nas ações para elementos que casem com estes seletores.
|
|
41
|
+
* Os demais viram uma referência opaca. Senhas nunca são gravadas.
|
|
42
|
+
*/
|
|
43
|
+
allowedInputSelectors: string[];
|
|
44
|
+
/** Grava o valor selecionado/estado de select, checkbox e radio. Padrão: true. */
|
|
45
|
+
captureChoiceValues: boolean;
|
|
46
|
+
/** Elementos que não aparecem no replay nem nas ações. */
|
|
47
|
+
blockSelectors: string[];
|
|
48
|
+
/** Elementos cujo texto é mascarado no replay. */
|
|
49
|
+
maskTextSelectors: string[];
|
|
50
|
+
/** Elementos ignorados pelo Action Recorder. */
|
|
51
|
+
ignoreSelectors: string[];
|
|
52
|
+
/** URLs cujas requisições não são capturadas (string = prefixo/substring, RegExp). */
|
|
53
|
+
ignoreUrls: Array<string | RegExp>;
|
|
54
|
+
/** Cabeçalhos adicionais a remover. */
|
|
55
|
+
redactHeaders: string[];
|
|
56
|
+
/** Chaves adicionais (query/body/json) a redigir. */
|
|
57
|
+
redactKeys: string[];
|
|
58
|
+
/** Tamanho máximo (bytes) de cada body capturado. */
|
|
59
|
+
maxBodyBytes: number;
|
|
60
|
+
/** Content-types capturáveis como texto. */
|
|
61
|
+
textContentTypes: RegExp[];
|
|
62
|
+
/** Descrição da retenção local (ms) dos eventos não enviados no IndexedDB. */
|
|
63
|
+
localRetentionMs: number;
|
|
64
|
+
}
|
|
65
|
+
export interface TransportConfig {
|
|
66
|
+
batchMaxEvents: number;
|
|
67
|
+
batchMaxBytes: number;
|
|
68
|
+
flushIntervalMs: number;
|
|
69
|
+
maxMemoryEvents: number;
|
|
70
|
+
maxBufferBytes: number;
|
|
71
|
+
maxRetries: number;
|
|
72
|
+
retryBaseMs: number;
|
|
73
|
+
retryMaxMs: number;
|
|
74
|
+
/** Compressão gzip (requer suporte do backend: Content-Encoding: gzip). */
|
|
75
|
+
compress: boolean;
|
|
76
|
+
/** Limite para envio com keepalive no encerramento da página (browsers limitam a ~64KB). */
|
|
77
|
+
keepaliveMaxBytes: number;
|
|
78
|
+
/** Persistência em IndexedDB. Se indisponível, cai para memória. */
|
|
79
|
+
persist: boolean;
|
|
80
|
+
requestTimeoutMs: number;
|
|
81
|
+
}
|
|
82
|
+
export interface OffscriptConfig {
|
|
83
|
+
apiUrl: string;
|
|
84
|
+
platformId: string;
|
|
85
|
+
/** Versão/build da aplicação testada. */
|
|
86
|
+
appVersion?: string;
|
|
87
|
+
recording?: Partial<RecordingConfig>;
|
|
88
|
+
privacy?: Partial<PrivacyConfig>;
|
|
89
|
+
transport?: Partial<TransportConfig>;
|
|
90
|
+
/** Janela (ms) para relacionar requisições/erros à ação anterior. */
|
|
91
|
+
correlationWindowMs?: number;
|
|
92
|
+
/** Retoma automaticamente a sessão persistida em init() (após reload/navegação). Padrão: true. */
|
|
93
|
+
autoResume?: boolean;
|
|
94
|
+
/** Intervalo de heartbeat quando o backend não informar. */
|
|
95
|
+
heartbeatIntervalMs?: number;
|
|
96
|
+
/** Prefixo de chaves de storage. */
|
|
97
|
+
storagePrefix?: string;
|
|
98
|
+
debug?: boolean;
|
|
99
|
+
}
|
|
100
|
+
export interface ResolvedConfig {
|
|
101
|
+
apiUrl: string;
|
|
102
|
+
platformId: string;
|
|
103
|
+
appVersion?: string;
|
|
104
|
+
recording: RecordingConfig;
|
|
105
|
+
privacy: PrivacyConfig;
|
|
106
|
+
transport: TransportConfig;
|
|
107
|
+
correlationWindowMs: number;
|
|
108
|
+
autoResume: boolean;
|
|
109
|
+
heartbeatIntervalMs: number;
|
|
110
|
+
storagePrefix: string;
|
|
111
|
+
debug: boolean;
|
|
112
|
+
}
|
|
113
|
+
export declare const DEFAULT_RECORDING: RecordingConfig;
|
|
114
|
+
export declare const DEFAULT_PRIVACY: PrivacyConfig;
|
|
115
|
+
export declare const DEFAULT_TRANSPORT: TransportConfig;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export declare const SCHEMA_VERSION = "1.0";
|
|
2
|
+
export declare const SDK_VERSION = "0.1.0";
|
|
3
|
+
export type EventKind = "rrweb" | "action" | "http" | "console" | "error" | "context" | "iframe" | "lifecycle";
|
|
4
|
+
/** Envelope comum a todos os eventos. A ordem canônica é (tabId, sequence). */
|
|
5
|
+
export interface OffscriptEvent<T = unknown> {
|
|
6
|
+
/** Identificador único (idempotência). */
|
|
7
|
+
id: string;
|
|
8
|
+
kind: EventKind;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
executionId: string;
|
|
11
|
+
tabId: string;
|
|
12
|
+
pageId: string;
|
|
13
|
+
/** Sequência monotônica por aba, persistida entre páginas. */
|
|
14
|
+
sequence: number;
|
|
15
|
+
/** Momento em que o fato ocorreu (epoch ms). */
|
|
16
|
+
timestamp: number;
|
|
17
|
+
/** Ação do usuário mais recente dentro da janela de correlação. */
|
|
18
|
+
relatedActionId?: string;
|
|
19
|
+
relatedActionDeltaMs?: number;
|
|
20
|
+
data: T;
|
|
21
|
+
}
|
|
22
|
+
export interface EventBatch {
|
|
23
|
+
schemaVersion: string;
|
|
24
|
+
batchId: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
executionId: string;
|
|
27
|
+
tabId: string;
|
|
28
|
+
sequenceStart: number;
|
|
29
|
+
sequenceEnd: number;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
sdkVersion: string;
|
|
32
|
+
events: OffscriptEvent[];
|
|
33
|
+
}
|
|
34
|
+
/** Contrato das ações semânticas (versionado por ACTION_SCHEMA_VERSION). */
|
|
35
|
+
export declare const ACTION_SCHEMA_VERSION = "1.0";
|
|
36
|
+
export type ActionType = "click" | "dblclick" | "hover" | "mousemove" | "drag" | "input" | "keydown" | "select" | "check" | "scroll" | "navigate" | "reload" | "focus" | "blur";
|
|
37
|
+
export interface SelectorCandidate {
|
|
38
|
+
strategy: "testid" | "attribute" | "role" | "label" | "placeholder" | "id" | "css";
|
|
39
|
+
/** Seletor em sintaxe Playwright (ex.: `role=button[name="Salvar"]`). */
|
|
40
|
+
selector: string;
|
|
41
|
+
/** Único no documento no momento da ação. */
|
|
42
|
+
unique: boolean;
|
|
43
|
+
matches: number;
|
|
44
|
+
}
|
|
45
|
+
export interface ElementDescriptor {
|
|
46
|
+
tag: string;
|
|
47
|
+
id?: string;
|
|
48
|
+
role?: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
text?: string;
|
|
51
|
+
type?: string;
|
|
52
|
+
testId?: string;
|
|
53
|
+
classes?: string[];
|
|
54
|
+
href?: string;
|
|
55
|
+
inputName?: string;
|
|
56
|
+
rect?: {
|
|
57
|
+
x: number;
|
|
58
|
+
y: number;
|
|
59
|
+
width: number;
|
|
60
|
+
height: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface RecordedAction {
|
|
64
|
+
schemaVersion: string;
|
|
65
|
+
id: string;
|
|
66
|
+
sessionId: string;
|
|
67
|
+
executionId: string;
|
|
68
|
+
timestamp: number;
|
|
69
|
+
/** Mesma sequência do envelope do evento. */
|
|
70
|
+
sequence: number;
|
|
71
|
+
type: ActionType;
|
|
72
|
+
url: string;
|
|
73
|
+
selector?: string;
|
|
74
|
+
selectorCandidates?: SelectorCandidate[];
|
|
75
|
+
selectorUnique?: boolean;
|
|
76
|
+
element?: ElementDescriptor;
|
|
77
|
+
coordinates?: {
|
|
78
|
+
x: number;
|
|
79
|
+
y: number;
|
|
80
|
+
};
|
|
81
|
+
viewport?: {
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
};
|
|
85
|
+
payload?: Record<string, unknown>;
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
export interface HttpEventData {
|
|
89
|
+
requestId: string;
|
|
90
|
+
transport: "fetch" | "xhr";
|
|
91
|
+
url: string;
|
|
92
|
+
method: string;
|
|
93
|
+
query?: Record<string, string | string[]>;
|
|
94
|
+
requestHeaders?: Record<string, string>;
|
|
95
|
+
requestBody?: CapturedBody;
|
|
96
|
+
status?: number;
|
|
97
|
+
statusText?: string;
|
|
98
|
+
responseHeaders?: Record<string, string>;
|
|
99
|
+
responseBody?: CapturedBody;
|
|
100
|
+
startedAt: number;
|
|
101
|
+
endedAt: number;
|
|
102
|
+
durationMs: number;
|
|
103
|
+
/** fetch: tempo até os headers; xhr: até loadend. */
|
|
104
|
+
error?: {
|
|
105
|
+
name: string;
|
|
106
|
+
message: string;
|
|
107
|
+
};
|
|
108
|
+
aborted?: boolean;
|
|
109
|
+
/** Quantas requisições estavam em andamento quando esta começou. */
|
|
110
|
+
concurrent: number;
|
|
111
|
+
}
|
|
112
|
+
export interface CapturedBody {
|
|
113
|
+
kind: "text" | "json" | "form" | "binary" | "omitted";
|
|
114
|
+
value?: unknown;
|
|
115
|
+
size?: number;
|
|
116
|
+
truncated?: boolean;
|
|
117
|
+
reason?: string;
|
|
118
|
+
}
|
|
119
|
+
export interface ConsoleEventData {
|
|
120
|
+
level: "log" | "info" | "warn" | "error";
|
|
121
|
+
args: unknown[];
|
|
122
|
+
message: string;
|
|
123
|
+
stack?: string;
|
|
124
|
+
url: string;
|
|
125
|
+
}
|
|
126
|
+
export interface ErrorEventData {
|
|
127
|
+
source: "window.onerror" | "unhandledrejection" | "resource";
|
|
128
|
+
message: string;
|
|
129
|
+
name?: string;
|
|
130
|
+
stack?: string;
|
|
131
|
+
file?: string;
|
|
132
|
+
line?: number;
|
|
133
|
+
column?: number;
|
|
134
|
+
url: string;
|
|
135
|
+
}
|
|
136
|
+
export interface ContextEventData {
|
|
137
|
+
reason: "initial" | "viewport" | "navigation" | "visibility" | "page-load";
|
|
138
|
+
url: string;
|
|
139
|
+
referrer?: string;
|
|
140
|
+
title?: string;
|
|
141
|
+
navigationType?: string;
|
|
142
|
+
browser?: {
|
|
143
|
+
name?: string;
|
|
144
|
+
version?: string;
|
|
145
|
+
};
|
|
146
|
+
os?: string;
|
|
147
|
+
userAgent: string;
|
|
148
|
+
screen: {
|
|
149
|
+
width: number;
|
|
150
|
+
height: number;
|
|
151
|
+
};
|
|
152
|
+
viewport: {
|
|
153
|
+
width: number;
|
|
154
|
+
height: number;
|
|
155
|
+
};
|
|
156
|
+
devicePixelRatio: number;
|
|
157
|
+
language: string;
|
|
158
|
+
timezone?: string;
|
|
159
|
+
online: boolean;
|
|
160
|
+
visibility?: string;
|
|
161
|
+
platformId: string;
|
|
162
|
+
testCaseId: string;
|
|
163
|
+
executionId: string;
|
|
164
|
+
sdkVersion: string;
|
|
165
|
+
appVersion?: string;
|
|
166
|
+
}
|
|
167
|
+
export interface IframeEventData {
|
|
168
|
+
phase: "present" | "removed" | "focus" | "blur" | "resized";
|
|
169
|
+
provider?: "seatsio" | "unknown";
|
|
170
|
+
origin?: string;
|
|
171
|
+
path?: string;
|
|
172
|
+
crossOrigin: boolean;
|
|
173
|
+
rect: {
|
|
174
|
+
x: number;
|
|
175
|
+
y: number;
|
|
176
|
+
width: number;
|
|
177
|
+
height: number;
|
|
178
|
+
};
|
|
179
|
+
index: number;
|
|
180
|
+
limitation: string;
|
|
181
|
+
}
|
|
182
|
+
export interface LifecycleEventData {
|
|
183
|
+
type: "session-start" | "session-resume" | "page-start" | "page-end" | "pause" | "resume" | "stop" | "navigation" | "overflow" | "recorder-error";
|
|
184
|
+
detail?: Record<string, unknown>;
|
|
185
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type RecordingStatus = "idle" | "initializing" | "recording" | "paused" | "stopping" | "stopped" | "error";
|
|
2
|
+
export interface StartRecordingOptions {
|
|
3
|
+
testCaseId: string;
|
|
4
|
+
executionId: string;
|
|
5
|
+
/** Token de curta duração emitido pelo backend da Offscript. */
|
|
6
|
+
sessionToken: string;
|
|
7
|
+
}
|
|
8
|
+
/** Estado persistido em sessionStorage (escopo: aba). */
|
|
9
|
+
export interface PersistedSessionState {
|
|
10
|
+
version: 1;
|
|
11
|
+
tabId: string;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
executionId: string;
|
|
14
|
+
testCaseId: string;
|
|
15
|
+
platformId: string;
|
|
16
|
+
sessionToken: string;
|
|
17
|
+
status: "recording" | "paused";
|
|
18
|
+
/** Último número de sequência emitido por esta aba. */
|
|
19
|
+
sequence: number;
|
|
20
|
+
/** Número de páginas já gravadas nesta aba. */
|
|
21
|
+
pageCount: number;
|
|
22
|
+
heartbeatIntervalMs?: number;
|
|
23
|
+
expiresAt?: number;
|
|
24
|
+
updatedAt: number;
|
|
25
|
+
}
|
|
26
|
+
export interface SessionInfo {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
executionId: string;
|
|
29
|
+
testCaseId: string;
|
|
30
|
+
platformId: string;
|
|
31
|
+
tabId: string;
|
|
32
|
+
pageId: string;
|
|
33
|
+
startedAt: number;
|
|
34
|
+
}
|
|
35
|
+
export interface OffscriptState {
|
|
36
|
+
status: RecordingStatus;
|
|
37
|
+
session: SessionInfo | null;
|
|
38
|
+
pending: number;
|
|
39
|
+
lastError?: {
|
|
40
|
+
code: string;
|
|
41
|
+
message: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Resposta de POST /sdk/sessions */
|
|
45
|
+
export interface CreateSessionResponse {
|
|
46
|
+
sessionId: string;
|
|
47
|
+
recording: {
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
reason?: string;
|
|
50
|
+
};
|
|
51
|
+
expiresAt?: number;
|
|
52
|
+
heartbeatIntervalMs?: number;
|
|
53
|
+
/** Token renovado (opcional). */
|
|
54
|
+
sessionToken?: string;
|
|
55
|
+
limits?: {
|
|
56
|
+
maxBatchBytes?: number;
|
|
57
|
+
maxBatchEvents?: number;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export interface HeartbeatResponse {
|
|
61
|
+
status: "active" | "disabled" | "expired";
|
|
62
|
+
sessionToken?: string;
|
|
63
|
+
expiresAt?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface EventsAck {
|
|
66
|
+
batchId: string;
|
|
67
|
+
/** Maior sequência persistida contiguamente para a aba. */
|
|
68
|
+
persistedThrough?: number;
|
|
69
|
+
accepted: number;
|
|
70
|
+
duplicate?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface StopSummary {
|
|
73
|
+
sessionId: string;
|
|
74
|
+
flushed: boolean;
|
|
75
|
+
pendingEvents: number;
|
|
76
|
+
completed: boolean;
|
|
77
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type OffscriptErrorCode = "CONFIG_INVALID" | "AUTH_FAILED" | "SESSION_EXPIRED" | "SESSION_DISABLED" | "INVALID_STATE" | "TRANSPORT_FAILED" | "PAYLOAD_TOO_LARGE" | "NOT_SUPPORTED" | "INTERNAL";
|
|
2
|
+
export declare class OffscriptError extends Error {
|
|
3
|
+
readonly code: OffscriptErrorCode;
|
|
4
|
+
readonly status?: number;
|
|
5
|
+
readonly cause?: unknown;
|
|
6
|
+
constructor(code: OffscriptErrorCode, message: string, opts?: {
|
|
7
|
+
status?: number;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare class ConfigError extends OffscriptError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class AuthError extends OffscriptError {
|
|
15
|
+
constructor(message: string, status?: number);
|
|
16
|
+
}
|
|
17
|
+
export declare class SessionExpiredError extends OffscriptError {
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class SessionDisabledError extends OffscriptError {
|
|
21
|
+
constructor(message?: string);
|
|
22
|
+
}
|
|
23
|
+
export declare class InvalidStateError extends OffscriptError {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class TransportError extends OffscriptError {
|
|
27
|
+
readonly retryable: boolean;
|
|
28
|
+
constructor(message: string, opts: {
|
|
29
|
+
status?: number;
|
|
30
|
+
retryable: boolean;
|
|
31
|
+
cause?: unknown;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Emissor tipado mínimo. */
|
|
35
|
+
export declare class Emitter<M extends Record<string, unknown>> {
|
|
36
|
+
private handlers;
|
|
37
|
+
on<K extends keyof M>(type: K, fn: (payload: M[K]) => void): () => void;
|
|
38
|
+
off<K extends keyof M>(type: K, fn: (payload: M[K]) => void): void;
|
|
39
|
+
emit<K extends keyof M>(type: K, payload: M[K]): void;
|
|
40
|
+
clear(): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function uid(prefix?: string): string;
|