@sudo-ping-pong/prism-expo-client 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/dist/buffer.d.ts +8 -0
  4. package/dist/buffer.d.ts.map +1 -0
  5. package/dist/buffer.js +19 -0
  6. package/dist/buffer.js.map +1 -0
  7. package/dist/capture.d.ts +5 -0
  8. package/dist/capture.d.ts.map +1 -0
  9. package/dist/capture.js +36 -0
  10. package/dist/capture.js.map +1 -0
  11. package/dist/command-handler.d.ts +2 -0
  12. package/dist/command-handler.d.ts.map +1 -0
  13. package/dist/command-handler.js +14 -0
  14. package/dist/command-handler.js.map +1 -0
  15. package/dist/config.d.ts +22 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +15 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/index.d.ts +38 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +103 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/middleware/redux.d.ts +28 -0
  24. package/dist/middleware/redux.d.ts.map +1 -0
  25. package/dist/middleware/redux.js +94 -0
  26. package/dist/middleware/redux.js.map +1 -0
  27. package/dist/middleware/zustand.d.ts +11 -0
  28. package/dist/middleware/zustand.d.ts.map +1 -0
  29. package/dist/middleware/zustand.js +37 -0
  30. package/dist/middleware/zustand.js.map +1 -0
  31. package/dist/patches/axios.d.ts +4 -0
  32. package/dist/patches/axios.d.ts.map +1 -0
  33. package/dist/patches/axios.js +140 -0
  34. package/dist/patches/axios.js.map +1 -0
  35. package/dist/patches/capture-guard.d.ts +5 -0
  36. package/dist/patches/capture-guard.d.ts.map +1 -0
  37. package/dist/patches/capture-guard.js +24 -0
  38. package/dist/patches/capture-guard.js.map +1 -0
  39. package/dist/patches/console.d.ts +2 -0
  40. package/dist/patches/console.d.ts.map +1 -0
  41. package/dist/patches/console.js +21 -0
  42. package/dist/patches/console.js.map +1 -0
  43. package/dist/patches/fetch.d.ts +4 -0
  44. package/dist/patches/fetch.d.ts.map +1 -0
  45. package/dist/patches/fetch.js +106 -0
  46. package/dist/patches/fetch.js.map +1 -0
  47. package/dist/patches/xhr.d.ts +2 -0
  48. package/dist/patches/xhr.d.ts.map +1 -0
  49. package/dist/patches/xhr.js +74 -0
  50. package/dist/patches/xhr.js.map +1 -0
  51. package/dist/profiler.d.ts +9 -0
  52. package/dist/profiler.d.ts.map +1 -0
  53. package/dist/profiler.js +19 -0
  54. package/dist/profiler.js.map +1 -0
  55. package/dist/transport-registry.d.ts +8 -0
  56. package/dist/transport-registry.d.ts.map +1 -0
  57. package/dist/transport-registry.js +11 -0
  58. package/dist/transport-registry.js.map +1 -0
  59. package/dist/transport.d.ts +38 -0
  60. package/dist/transport.d.ts.map +1 -0
  61. package/dist/transport.js +153 -0
  62. package/dist/transport.js.map +1 -0
  63. package/package.json +111 -0
  64. package/src/buffer.ts +23 -0
  65. package/src/capture.ts +40 -0
  66. package/src/command-handler.ts +15 -0
  67. package/src/config.ts +29 -0
  68. package/src/env.d.ts +1 -0
  69. package/src/index.ts +148 -0
  70. package/src/middleware/redux.ts +126 -0
  71. package/src/middleware/zustand.ts +58 -0
  72. package/src/patches/axios.ts +182 -0
  73. package/src/patches/capture-guard.ts +24 -0
  74. package/src/patches/console.ts +27 -0
  75. package/src/patches/fetch.ts +118 -0
  76. package/src/patches/xhr.ts +103 -0
  77. package/src/profiler.tsx +36 -0
  78. package/src/transport-registry.ts +20 -0
  79. package/src/transport.ts +198 -0
@@ -0,0 +1,58 @@
1
+ import type { StatePayload } from '@sudo-ping-pong/prism-protocol';
2
+ import { captureStateEvent } from '../capture';
3
+
4
+ export interface PrismZustandOptions {
5
+ storeId?: string;
6
+ }
7
+
8
+ type SetState<T> = (
9
+ partial: T | Partial<T> | ((state: T) => T | Partial<T>),
10
+ replace?: boolean,
11
+ actionName?: string,
12
+ ) => void;
13
+
14
+ type StateCreator<T> = (
15
+ set: SetState<T>,
16
+ get: () => T,
17
+ api: unknown,
18
+ ) => T;
19
+
20
+ /**
21
+ * Wrap a Zustand StateCreator to capture state mutations in Prism.
22
+ */
23
+ export function prismZustandMiddleware<T>(storeIdOrOptions?: string | PrismZustandOptions) {
24
+ const storeId =
25
+ typeof storeIdOrOptions === 'string'
26
+ ? storeIdOrOptions
27
+ : (storeIdOrOptions?.storeId ?? 'default');
28
+
29
+ return (creator: StateCreator<T>): StateCreator<T> => {
30
+ return (set, get, api) => {
31
+ const wrappedSet: SetState<T> = (partial, replace, actionName) => {
32
+ const prevState = get();
33
+ set(partial, replace);
34
+ const nextState = get();
35
+
36
+ const payload: StatePayload = {
37
+ actionLabel: actionName ?? 'anonymous',
38
+ payload: typeof partial === 'function' ? '(function)' : partial,
39
+ prevState: structuredCloneSafe(prevState),
40
+ nextState: structuredCloneSafe(nextState),
41
+ storeId,
42
+ source: 'zustand',
43
+ };
44
+ captureStateEvent(payload);
45
+ };
46
+
47
+ return creator(wrappedSet, get, api);
48
+ };
49
+ };
50
+ }
51
+
52
+ function structuredCloneSafe<T>(value: T): T {
53
+ try {
54
+ return JSON.parse(JSON.stringify(value)) as T;
55
+ } catch {
56
+ return value;
57
+ }
58
+ }
@@ -0,0 +1,182 @@
1
+ import type {
2
+ AxiosError,
3
+ AxiosInstance,
4
+ AxiosResponse,
5
+ AxiosStatic,
6
+ InternalAxiosRequestConfig,
7
+ } from 'axios';
8
+ import type { NetworkPayload } from '@sudo-ping-pong/prism-protocol';
9
+ import { captureNetworkEvent } from '../capture';
10
+ import { runWithoutNetworkCaptureAsync, shouldSkipNetworkCapture } from './capture-guard';
11
+ import { headersToRecord } from './fetch';
12
+
13
+ interface RequestMeta {
14
+ start: number;
15
+ method: string;
16
+ url: string;
17
+ requestHeaders: Record<string, string>;
18
+ requestBody?: string;
19
+ }
20
+
21
+ const patchedInstances = new WeakSet<AxiosInstance>();
22
+ const requestMeta = new WeakMap<InternalAxiosRequestConfig, RequestMeta>();
23
+
24
+ function serializeBody(data: unknown): string | undefined {
25
+ if (data == null) return undefined;
26
+ if (typeof data === 'string') return data;
27
+ if (typeof URLSearchParams !== 'undefined' && data instanceof URLSearchParams) {
28
+ return data.toString();
29
+ }
30
+ if (typeof FormData !== 'undefined' && data instanceof FormData) {
31
+ const parts: string[] = [];
32
+ data.forEach((value, key) => parts.push(`${key}=${String(value)}`));
33
+ return parts.join('&');
34
+ }
35
+ try {
36
+ return JSON.stringify(data);
37
+ } catch {
38
+ return String(data);
39
+ }
40
+ }
41
+
42
+ function flattenHeaders(headers: InternalAxiosRequestConfig['headers']): Record<string, string> {
43
+ if (!headers) return {};
44
+ if (typeof (headers as { toJSON?: () => Record<string, string> }).toJSON === 'function') {
45
+ return (headers as { toJSON: () => Record<string, string> }).toJSON();
46
+ }
47
+ return headersToRecord(headers as Record<string, string>);
48
+ }
49
+
50
+ function resolveUrl(config: InternalAxiosRequestConfig): string {
51
+ const url = config.url ?? '';
52
+ if (/^https?:\/\//i.test(url)) return url;
53
+ const base = config.baseURL ?? '';
54
+ if (!base) return url;
55
+ return `${base.replace(/\/$/, '')}/${url.replace(/^\//, '')}`;
56
+ }
57
+
58
+ function captureFromMeta(
59
+ meta: RequestMeta,
60
+ status: number,
61
+ statusText: string,
62
+ responseHeaders: Record<string, string>,
63
+ responseBody?: string,
64
+ ): void {
65
+ const payload: NetworkPayload = {
66
+ method: meta.method,
67
+ url: meta.url,
68
+ status,
69
+ statusText,
70
+ requestHeaders: meta.requestHeaders,
71
+ responseHeaders,
72
+ requestBody: meta.requestBody,
73
+ responseBody,
74
+ durationMs: Date.now() - meta.start,
75
+ initiator: 'axios',
76
+ };
77
+ captureNetworkEvent(payload);
78
+ }
79
+
80
+ function captureFromResponse(response: AxiosResponse): void {
81
+ const meta = requestMeta.get(response.config);
82
+ if (!meta) return;
83
+
84
+ const responseHeaders = flattenHeaders(response.headers as InternalAxiosRequestConfig['headers']);
85
+ const responseBody = serializeBody(response.data);
86
+
87
+ captureFromMeta(meta, response.status, response.statusText, responseHeaders, responseBody);
88
+ requestMeta.delete(response.config);
89
+ }
90
+
91
+ function captureFromError(error: AxiosError): void {
92
+ const config = error.config;
93
+ if (!config) return;
94
+
95
+ const meta = requestMeta.get(config);
96
+ if (!meta) return;
97
+
98
+ if (error.response) {
99
+ const responseHeaders = flattenHeaders(
100
+ error.response.headers as InternalAxiosRequestConfig['headers'],
101
+ );
102
+ captureFromMeta(
103
+ meta,
104
+ error.response.status,
105
+ error.response.statusText,
106
+ responseHeaders,
107
+ serializeBody(error.response.data),
108
+ );
109
+ } else {
110
+ captureFromMeta(meta, 0, error.message || 'Network Error', {}, undefined);
111
+ }
112
+
113
+ requestMeta.delete(config);
114
+ }
115
+
116
+ function patchAxiosInstance(instance: AxiosInstance): void {
117
+ if (patchedInstances.has(instance)) return;
118
+ patchedInstances.add(instance);
119
+
120
+ instance.interceptors.request.use((config) => {
121
+ requestMeta.set(config, {
122
+ start: Date.now(),
123
+ method: (config.method ?? 'get').toUpperCase(),
124
+ url: resolveUrl(config),
125
+ requestHeaders: flattenHeaders(config.headers),
126
+ requestBody: serializeBody(config.data),
127
+ });
128
+ return config;
129
+ });
130
+
131
+ instance.interceptors.response.use(
132
+ (response) => {
133
+ captureFromResponse(response);
134
+ return response;
135
+ },
136
+ (error: AxiosError) => {
137
+ captureFromError(error);
138
+ return Promise.reject(error);
139
+ },
140
+ );
141
+
142
+ const originalRequest = instance.request.bind(instance);
143
+ instance.request = ((config) =>
144
+ runWithoutNetworkCaptureAsync(() => originalRequest(config))) as typeof instance.request;
145
+ }
146
+
147
+ function resolveAxiosModule(axiosModule?: AxiosStatic): AxiosStatic | null {
148
+ if (axiosModule) return axiosModule;
149
+
150
+ const req = (globalThis as { require?: (id: string) => unknown }).require;
151
+ if (!req) return null;
152
+
153
+ try {
154
+ const mod = req('axios') as AxiosStatic | { default: AxiosStatic };
155
+ return ('default' in mod ? mod.default : mod) as AxiosStatic;
156
+ } catch {
157
+ return null;
158
+ }
159
+ }
160
+
161
+ export function installAxiosPatch(axiosModule?: AxiosStatic): () => void {
162
+ const axios = resolveAxiosModule(axiosModule);
163
+ if (!axios) return () => {};
164
+
165
+ patchAxiosInstance(axios);
166
+
167
+ const originalCreate = axios.create.bind(axios);
168
+ axios.create = ((...args: Parameters<AxiosStatic['create']>) => {
169
+ const instance = originalCreate(...args);
170
+ patchAxiosInstance(instance);
171
+ return instance;
172
+ }) as AxiosStatic['create'];
173
+
174
+ return () => {
175
+ // Axios has no public API to remove interceptors from the default instance;
176
+ // patch is idempotent per instance for the app lifetime.
177
+ };
178
+ }
179
+
180
+ export function isAxiosNetworkCaptureActive(): boolean {
181
+ return shouldSkipNetworkCapture();
182
+ }
@@ -0,0 +1,24 @@
1
+ let depth = 0;
2
+
3
+ /** Suppress fetch/XHR patches while axios handles capture (avoids duplicate events). */
4
+ export function runWithoutNetworkCapture<T>(fn: () => T): T {
5
+ depth += 1;
6
+ try {
7
+ return fn();
8
+ } finally {
9
+ depth -= 1;
10
+ }
11
+ }
12
+
13
+ export async function runWithoutNetworkCaptureAsync<T>(fn: () => Promise<T>): Promise<T> {
14
+ depth += 1;
15
+ try {
16
+ return await fn();
17
+ } finally {
18
+ depth -= 1;
19
+ }
20
+ }
21
+
22
+ export function shouldSkipNetworkCapture(): boolean {
23
+ return depth > 0;
24
+ }
@@ -0,0 +1,27 @@
1
+ import type { LogLevel } from '@sudo-ping-pong/prism-protocol';
2
+ import { captureLogEvent } from '../capture';
3
+
4
+ type ConsoleFn = (...args: unknown[]) => void;
5
+
6
+ const LEVELS: LogLevel[] = ['log', 'warn', 'error'];
7
+
8
+ export function installConsolePatch(): () => void {
9
+ const originals = new Map<LogLevel, ConsoleFn>();
10
+
11
+ for (const level of LEVELS) {
12
+ const original = console[level].bind(console) as ConsoleFn;
13
+ originals.set(level, original);
14
+
15
+ console[level] = (...args: unknown[]) => {
16
+ original(...args);
17
+ captureLogEvent(level, args);
18
+ };
19
+ }
20
+
21
+ return () => {
22
+ for (const level of LEVELS) {
23
+ const original = originals.get(level);
24
+ if (original) console[level] = original as typeof console.log;
25
+ }
26
+ };
27
+ }
@@ -0,0 +1,118 @@
1
+ import type { NetworkPayload } from '@sudo-ping-pong/prism-protocol';
2
+ import { captureNetworkEvent } from '../capture';
3
+ import { shouldSkipNetworkCapture } from './capture-guard';
4
+
5
+ export function headersToRecord(
6
+ headers: Headers | Record<string, string> | undefined,
7
+ ): Record<string, string> {
8
+ if (!headers) return {};
9
+ if (headers instanceof Headers) {
10
+ const record: Record<string, string> = {};
11
+ headers.forEach((value, key) => {
12
+ record[key] = value;
13
+ });
14
+ return record;
15
+ }
16
+ return { ...headers };
17
+ }
18
+
19
+ export async function bodyToString(body: unknown): Promise<string | undefined> {
20
+ if (body == null) return undefined;
21
+ if (typeof body === 'string') return body;
22
+ if (body instanceof FormData) {
23
+ const parts: string[] = [];
24
+ body.forEach((value, key) => {
25
+ parts.push(`${key}=${String(value)}`);
26
+ });
27
+ return parts.join('&');
28
+ }
29
+ if (body instanceof URLSearchParams) return body.toString();
30
+ try {
31
+ return JSON.stringify(body);
32
+ } catch {
33
+ return String(body);
34
+ }
35
+ }
36
+
37
+ export function installFetchPatch(): () => void {
38
+ const originalFetch = globalThis.fetch;
39
+
40
+ globalThis.fetch = async function patchedFetch(
41
+ input: RequestInfo | URL,
42
+ init?: RequestInit,
43
+ ): Promise<Response> {
44
+ if (shouldSkipNetworkCapture()) {
45
+ return originalFetch(input, init);
46
+ }
47
+
48
+ const start = Date.now();
49
+ const url =
50
+ typeof input === 'string'
51
+ ? input
52
+ : input instanceof URL
53
+ ? input.href
54
+ : input.url;
55
+ const method = init?.method ?? (input instanceof Request ? input.method : 'GET');
56
+
57
+ let requestHeaders: Record<string, string> = {};
58
+ let requestBody: string | undefined;
59
+
60
+ if (input instanceof Request) {
61
+ requestHeaders = headersToRecord(input.headers);
62
+ }
63
+ if (init?.headers) {
64
+ requestHeaders = { ...requestHeaders, ...headersToRecord(init.headers as Headers) };
65
+ }
66
+ requestBody = await bodyToString(init?.body);
67
+
68
+ let response: Response;
69
+ let error: unknown;
70
+
71
+ try {
72
+ response = await originalFetch(input, init);
73
+ } catch (err) {
74
+ error = err;
75
+ const payload: NetworkPayload = {
76
+ method: method.toUpperCase(),
77
+ url,
78
+ status: 0,
79
+ statusText: 'Network Error',
80
+ requestHeaders,
81
+ responseHeaders: {},
82
+ requestBody,
83
+ durationMs: Date.now() - start,
84
+ initiator: 'fetch',
85
+ };
86
+ captureNetworkEvent(payload);
87
+ throw err;
88
+ }
89
+
90
+ const cloned = response.clone();
91
+ let responseBody: string | undefined;
92
+ try {
93
+ responseBody = await cloned.text();
94
+ } catch {
95
+ responseBody = undefined;
96
+ }
97
+
98
+ const payload: NetworkPayload = {
99
+ method: method.toUpperCase(),
100
+ url,
101
+ status: response.status,
102
+ statusText: response.statusText,
103
+ requestHeaders,
104
+ responseHeaders: headersToRecord(response.headers),
105
+ requestBody,
106
+ responseBody,
107
+ durationMs: Date.now() - start,
108
+ initiator: 'fetch',
109
+ };
110
+
111
+ captureNetworkEvent(payload);
112
+ return response;
113
+ } as typeof fetch;
114
+
115
+ return () => {
116
+ globalThis.fetch = originalFetch;
117
+ };
118
+ }
@@ -0,0 +1,103 @@
1
+ import type { NetworkPayload } from '@sudo-ping-pong/prism-protocol';
2
+ import { captureNetworkEvent } from '../capture';
3
+ import { shouldSkipNetworkCapture } from './capture-guard';
4
+
5
+ type XHROpen = typeof XMLHttpRequest.prototype.open;
6
+ type XHRSend = typeof XMLHttpRequest.prototype.send;
7
+ type XHRSetHeader = typeof XMLHttpRequest.prototype.setRequestHeader;
8
+
9
+ export function installXhrPatch(): () => void {
10
+ const originalOpen = XMLHttpRequest.prototype.open as XHROpen;
11
+ const originalSend = XMLHttpRequest.prototype.send as XHRSend;
12
+ const originalSetHeader = XMLHttpRequest.prototype.setRequestHeader as XHRSetHeader;
13
+
14
+ XMLHttpRequest.prototype.open = function (
15
+ method: string,
16
+ url: string | URL,
17
+ async?: boolean,
18
+ username?: string | null,
19
+ password?: string | null,
20
+ ) {
21
+ (this as XMLHttpRequest & { _prism?: { method: string; url: string; headers: Record<string, string>; start: number } })._prism = {
22
+ method: method.toUpperCase(),
23
+ url: typeof url === 'string' ? url : url.href,
24
+ headers: {},
25
+ start: 0,
26
+ };
27
+ return originalOpen.call(this, method, url, async ?? true, username, password);
28
+ };
29
+
30
+ XMLHttpRequest.prototype.setRequestHeader = function (name: string, value: string) {
31
+ const prism = (this as XMLHttpRequest & { _prism?: { headers: Record<string, string> } })._prism;
32
+ if (prism) prism.headers[name] = value;
33
+ return originalSetHeader.call(this, name, value);
34
+ };
35
+
36
+ XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {
37
+ if (shouldSkipNetworkCapture()) {
38
+ return originalSend.call(this, body);
39
+ }
40
+
41
+ const prism = (this as XMLHttpRequest & {
42
+ _prism?: {
43
+ method: string;
44
+ url: string;
45
+ headers: Record<string, string>;
46
+ start: number;
47
+ };
48
+ })._prism;
49
+
50
+ if (prism) {
51
+ prism.start = Date.now();
52
+ }
53
+
54
+ const requestBody =
55
+ body == null
56
+ ? undefined
57
+ : typeof body === 'string'
58
+ ? body
59
+ : body instanceof URLSearchParams
60
+ ? body.toString()
61
+ : String(body);
62
+
63
+ const onLoadEnd = () => {
64
+ if (!prism) return;
65
+
66
+ let responseHeaders: Record<string, string> = {};
67
+ const raw = this.getAllResponseHeaders();
68
+ if (raw) {
69
+ raw.split('\r\n').forEach((line) => {
70
+ const idx = line.indexOf(':');
71
+ if (idx > 0) {
72
+ responseHeaders[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
73
+ }
74
+ });
75
+ }
76
+
77
+ const payload: NetworkPayload = {
78
+ method: prism.method,
79
+ url: prism.url,
80
+ status: this.status,
81
+ statusText: this.statusText,
82
+ requestHeaders: prism.headers,
83
+ responseHeaders,
84
+ requestBody,
85
+ responseBody: this.responseType === '' || this.responseType === 'text' ? String(this.responseText ?? '') : undefined,
86
+ durationMs: Date.now() - prism.start,
87
+ initiator: 'xhr',
88
+ };
89
+
90
+ captureNetworkEvent(payload);
91
+ this.removeEventListener('loadend', onLoadEnd);
92
+ };
93
+
94
+ this.addEventListener('loadend', onLoadEnd);
95
+ return originalSend.call(this, body);
96
+ };
97
+
98
+ return () => {
99
+ XMLHttpRequest.prototype.open = originalOpen;
100
+ XMLHttpRequest.prototype.send = originalSend;
101
+ XMLHttpRequest.prototype.setRequestHeader = originalSetHeader;
102
+ };
103
+ }
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Profiler, type ProfilerOnRenderCallback } from 'react';
3
+ import { createEnvelope } from '@sudo-ping-pong/prism-protocol';
4
+ import type { PerformancePayload } from '@sudo-ping-pong/prism-protocol';
5
+ import { sendToPrism } from './transport-registry';
6
+
7
+ export interface PrismProfilerProps {
8
+ /** Component label shown in the performance timeline */
9
+ id: string;
10
+ children: ReactNode;
11
+ }
12
+
13
+ const onRender: ProfilerOnRenderCallback = (
14
+ id,
15
+ phase,
16
+ actualDuration,
17
+ baseDuration,
18
+ ) => {
19
+ const payload: PerformancePayload = {
20
+ type: 'render',
21
+ componentId: id,
22
+ phase: phase === 'mount' ? 'mount' : phase === 'update' ? 'update' : 'nested-update',
23
+ actualDurationMs: actualDuration,
24
+ baseDurationMs: baseDuration,
25
+ };
26
+ sendToPrism(createEnvelope('PERFORMANCE', payload));
27
+ };
28
+
29
+ /** Wrap a subtree to capture React render timings in Prism DevTools. */
30
+ export function PrismProfiler({ id, children }: PrismProfilerProps) {
31
+ return (
32
+ <Profiler id={id} onRender={onRender}>
33
+ {children}
34
+ </Profiler>
35
+ );
36
+ }
@@ -0,0 +1,20 @@
1
+ import type { PrismEnvelope } from '@sudo-ping-pong/prism-protocol';
2
+ import type { PrismTransport } from './transport';
3
+
4
+ export interface PrismTransportSink {
5
+ send(envelope: PrismEnvelope): void;
6
+ }
7
+
8
+ let activeTransport: PrismTransportSink | null = null;
9
+
10
+ export function setActiveTransport(transport: PrismTransportSink | null): void {
11
+ activeTransport = transport;
12
+ }
13
+
14
+ export function getActiveTransport(): PrismTransportSink | null {
15
+ return activeTransport;
16
+ }
17
+
18
+ export function sendToPrism(envelope: PrismEnvelope): void {
19
+ activeTransport?.send(envelope);
20
+ }