diff --git a/packages/audience/core/src/consent.test.ts b/packages/audience/core/src/consent.test.ts index 18ec83286b..d6be33e8f9 100644 --- a/packages/audience/core/src/consent.test.ts +++ b/packages/audience/core/src/consent.test.ts @@ -138,25 +138,19 @@ describe('createConsentManager', () => { Object.defineProperty(navigator, 'globalPrivacyControl', { value: undefined, configurable: true }); }); - it('tracks gpc_consent_override metric with signal and configured level when GPC fires', () => { + it('tracks gpc_consent_overridden when GPC fires at init', () => { Object.defineProperty(navigator, 'globalPrivacyControl', { value: true, configurable: true }); const send = createMockSend(); createConsentManager(send, 'pk_imapik-test-local', 'anon-1', 'pixel', 'full'); - const expected = { - signal: 'gpc', requestedLevel: 'full', context: 'init', publishableKey: 'pk_imapik-test-local', - }; - expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden', expected); + expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden'); Object.defineProperty(navigator, 'globalPrivacyControl', { value: undefined, configurable: true }); }); - it('tracks gpc_consent_override metric with dnt signal when DNT fires', () => { + it('tracks gpc_consent_overridden when DNT fires at init', () => { Object.defineProperty(navigator, 'doNotTrack', { value: '1', configurable: true }); const send = createMockSend(); createConsentManager(send, 'pk_imapik-test-local', 'anon-1', 'pixel', 'anonymous'); - const expected = { - signal: 'dnt', requestedLevel: 'anonymous', context: 'init', publishableKey: 'pk_imapik-test-local', - }; - expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden', expected); + expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden'); Object.defineProperty(navigator, 'doNotTrack', { value: '0', configurable: true }); }); @@ -176,23 +170,17 @@ describe('createConsentManager', () => { const manager = createConsentManager(send, 'pk_imapik-test-local', 'anon-1', 'pixel', 'none'); (track as jest.Mock).mockClear(); manager.setLevel('full'); - const expected = { - signal: 'gpc', requestedLevel: 'full', context: 'runtime', publishableKey: 'pk_imapik-test-local', - }; - expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden', expected); + expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden'); Object.defineProperty(navigator, 'globalPrivacyControl', { value: undefined, configurable: true }); }); - it('tracks gpc_consent_overridden with dnt signal when blocked by DNT at runtime', () => { + it('tracks gpc_consent_overridden when setLevel is blocked by DNT', () => { Object.defineProperty(navigator, 'doNotTrack', { value: '1', configurable: true }); const send = createMockSend(); const manager = createConsentManager(send, 'pk_imapik-test-local', 'anon-1', 'pixel', 'none'); (track as jest.Mock).mockClear(); manager.setLevel('full'); - const expected = { - signal: 'dnt', requestedLevel: 'full', context: 'runtime', publishableKey: 'pk_imapik-test-local', - }; - expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden', expected); + expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden'); Object.defineProperty(navigator, 'doNotTrack', { value: '0', configurable: true }); }); diff --git a/packages/audience/core/src/consent.ts b/packages/audience/core/src/consent.ts index ab2c088880..e45748ace4 100644 --- a/packages/audience/core/src/consent.ts +++ b/packages/audience/core/src/consent.ts @@ -70,12 +70,7 @@ export function createConsentManager( ): ConsentManager { const privacySignalActive = detectDoNotTrack(); if (privacySignalActive) { - track('audience', 'gpc_consent_overridden', { - signal: resolvePrivacySignal(), - requestedLevel: initialLevel ?? 'none', - context: 'init', - publishableKey, - }); + track('audience', 'gpc_consent_overridden'); } let current: ConsentLevel = privacySignalActive ? 'none' : (initialLevel ?? 'none'); @@ -101,12 +96,7 @@ export function createConsentManager( const effective = signalActive ? 'none' : next; if (signalActive && effective !== next) { - track('audience', 'gpc_consent_overridden', { - signal: resolvePrivacySignal(), - requestedLevel: next, - context: 'runtime', - publishableKey, - }); + track('audience', 'gpc_consent_overridden'); } if (effective === current) return; diff --git a/packages/audience/core/src/transport.test.ts b/packages/audience/core/src/transport.test.ts index cbba348b1e..a88b95fb86 100644 --- a/packages/audience/core/src/transport.test.ts +++ b/packages/audience/core/src/transport.test.ts @@ -1,11 +1,10 @@ -import { track, trackError } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import { httpSend } from './transport'; import { TransportError } from './errors'; import type { BatchPayload } from './types'; jest.mock('@imtbl/metrics', () => ({ track: jest.fn(), - trackError: jest.fn(), })); const payload: BatchPayload = { @@ -24,7 +23,6 @@ const payload: BatchPayload = { }; const mockTrack = track as jest.Mock; -const mockTrackError = trackError as jest.Mock; describe('httpSend', () => { const originalFetch = global.fetch; @@ -32,7 +30,6 @@ describe('httpSend', () => { afterEach(() => { global.fetch = originalFetch; mockTrack.mockClear(); - mockTrackError.mockClear(); }); it('sends POST with correct headers and body', async () => { @@ -281,15 +278,10 @@ describe('httpSend', () => { await httpSend('https://example.com', 'pk', payload); - expect(mockTrackError).toHaveBeenCalledWith( + expect(mockTrack).toHaveBeenCalledWith( 'audience', 'transport_send', - expect.any(TransportError), - expect.objectContaining({ - errorName: 'TypeError', - online: true, - timeToFailureMs: expect.any(Number), - }), + { error: expect.any(TransportError) }, ); }); @@ -307,15 +299,10 @@ describe('httpSend', () => { jest.advanceTimersByTime(30_000); await sendPromise; - expect(mockTrackError).toHaveBeenCalledWith( + expect(mockTrack).toHaveBeenCalledWith( 'audience', 'transport_send', - expect.any(TransportError), - expect.objectContaining({ - errorName: 'AbortError', - online: true, - timeToFailureMs: expect.any(Number), - }), + { error: expect.any(TransportError) }, ); jest.useRealTimers(); @@ -327,18 +314,14 @@ describe('httpSend', () => { await httpSend('https://example.com', 'pk', payload); - expect(mockTrackError).toHaveBeenCalledWith( + expect(mockTrack).toHaveBeenCalledWith( 'audience', 'transport_send', - expect.any(TransportError), - expect.objectContaining({ - online: false, - timeToFailureMs: expect.any(Number), - }), + { error: expect.any(TransportError) }, ); }); - it('attaches online and timeToFailureMs to transport_send_failed on HTTP error', async () => { + it('tracks transport_send_failed on HTTP error', async () => { global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 500, @@ -348,18 +331,10 @@ describe('httpSend', () => { await httpSend('https://example.com', 'pk', payload); - expect(mockTrack).toHaveBeenCalledWith( - 'audience', - 'transport_send_failed', - expect.objectContaining({ - status: 500, - online: true, - timeToFailureMs: expect.any(Number), - }), - ); + expect(mockTrack).toHaveBeenCalledWith('audience', 'transport_send_failed'); }); - it('attaches online and timeToFailureMs to transport_send_failed on 429', async () => { + it('tracks transport_send_failed on 429', async () => { global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 429, @@ -368,15 +343,7 @@ describe('httpSend', () => { await httpSend('https://example.com', 'pk', payload); - expect(mockTrack).toHaveBeenCalledWith( - 'audience', - 'transport_send_failed', - expect.objectContaining({ - status: 429, - online: true, - timeToFailureMs: expect.any(Number), - }), - ); + expect(mockTrack).toHaveBeenCalledWith('audience', 'transport_send_failed'); }); }); }); diff --git a/packages/audience/core/src/transport.ts b/packages/audience/core/src/transport.ts index 447a039f6e..0a126ead11 100644 --- a/packages/audience/core/src/transport.ts +++ b/packages/audience/core/src/transport.ts @@ -1,7 +1,6 @@ -import { track, trackError } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import type { BatchPayload, ConsentUpdatePayload } from './types'; import { TransportError, type TransportResult } from './errors'; -import { isBrowser } from './utils'; export interface TransportOptions { method?: string; @@ -44,14 +43,6 @@ function parseRetryAfterMs(headers: Headers): number | null { return null; } -function safeOnline(): boolean | undefined { - try { - return isBrowser() ? navigator.onLine : undefined; - } catch { - return undefined; - } -} - async function parseBody(response: Response): Promise { const contentType = response.headers?.get?.('content-type') ?? ''; try { @@ -72,7 +63,6 @@ export const httpSend: HttpSend = async ( ) => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), HTTP_TIMEOUT_MS); - const startTime = Date.now(); try { const hasBody = payload !== undefined; @@ -88,11 +78,7 @@ export const httpSend: HttpSend = async ( }); if (response.status === 429) { - track('audience', 'transport_send_failed', { - status: 429, - online: safeOnline(), - timeToFailureMs: Date.now() - startTime, - }); + track('audience', 'transport_send_failed'); const retryAfterMs = parseRetryAfterMs(response.headers); return { ok: false, @@ -106,11 +92,7 @@ export const httpSend: HttpSend = async ( if (!response.ok) { const body = await parseBody(response); - track('audience', 'transport_send_failed', { - status: response.status, - online: safeOnline(), - timeToFailureMs: Date.now() - startTime, - }); + track('audience', 'transport_send_failed'); return { ok: false, error: new TransportError({ @@ -138,10 +120,7 @@ export const httpSend: HttpSend = async ( ) { const rejected = (body as { rejected?: number }).rejected ?? 0; if (rejected > 0) { - track('audience', 'transport_partial_rejected', { - status: response.status, - rejected, - }); + track('audience', 'transport_partial_rejected'); return { ok: false, error: new TransportError({ @@ -160,11 +139,7 @@ export const httpSend: HttpSend = async ( endpoint: url, cause: err, }); - trackError('audience', 'transport_send', error, { - errorName: err instanceof Error ? err.name : undefined, - online: safeOnline(), - timeToFailureMs: Date.now() - startTime, - }); + track('audience', 'transport_send', { error }); return { ok: false, error }; } finally { clearTimeout(timeoutId); diff --git a/packages/audience/pixel/src/stubs/metrics.ts b/packages/audience/pixel/src/stubs/metrics.ts index 126946a544..eb2ba1a53a 100644 --- a/packages/audience/pixel/src/stubs/metrics.ts +++ b/packages/audience/pixel/src/stubs/metrics.ts @@ -3,6 +3,4 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars export const track = (..._args: unknown[]): void => {}; // eslint-disable-next-line @typescript-eslint/no-unused-vars -export const trackError = (..._args: unknown[]): void => {}; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const trackDuration = (..._args: unknown[]): void => {}; +export const configure = (..._args: unknown[]): void => {}; diff --git a/packages/audience/sdk/src/sdk.test.ts b/packages/audience/sdk/src/sdk.test.ts index 1ad137cd21..fc9a0b0a89 100644 --- a/packages/audience/sdk/src/sdk.test.ts +++ b/packages/audience/sdk/src/sdk.test.ts @@ -7,7 +7,6 @@ import { LIBRARY_NAME } from './config'; jest.mock('@imtbl/metrics', () => ({ track: jest.fn(), - trackError: jest.fn(), })); const INGEST_PATH = '/v1/audience/messages'; @@ -1790,10 +1789,7 @@ describe('Audience', () => { // Consent stays at none — no events should be sent sdk.page(); expect(sentMessages()).toHaveLength(0); - const expected = { - signal: 'gpc', requestedLevel: 'anonymous', context: 'runtime', publishableKey: 'pk_imapik-test-local', - }; - expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden', expected); + expect(track).toHaveBeenCalledWith('audience', 'gpc_consent_overridden'); sdk.shutdown(); }); diff --git a/packages/audience/sdk/src/sdk.ts b/packages/audience/sdk/src/sdk.ts index 27a996fb59..090dfd52d9 100644 --- a/packages/audience/sdk/src/sdk.ts +++ b/packages/audience/sdk/src/sdk.ts @@ -37,7 +37,7 @@ import { isBrowser, setupAutocapture, } from '@imtbl/audience-core'; -import { adoptAnonymousId, resolvePrivacySignal } from '@imtbl/audience-core/internal'; +import { adoptAnonymousId } from '@imtbl/audience-core/internal'; import { track } from '@imtbl/metrics'; import { DebugLogger } from './debug'; import { REQUIRED_EVENT_PROPS, type AudienceEventName, type PropsFor } from './events'; @@ -450,12 +450,7 @@ export class Audience { const effective: ConsentLevel = privacySignalActive ? 'none' : level; if (privacySignalActive && effective !== level) { - track('audience', 'gpc_consent_overridden', { - signal: resolvePrivacySignal(), - requestedLevel: level, - context: 'runtime', - publishableKey: this.publishableKey, - }); + track('audience', 'gpc_consent_overridden'); this.debug.logWarning('GPC or DNT signal active: consent upgrade blocked.'); } diff --git a/packages/auth/src/Auth.test.ts b/packages/auth/src/Auth.test.ts index d2db66a19c..782aa77f62 100644 --- a/packages/auth/src/Auth.test.ts +++ b/packages/auth/src/Auth.test.ts @@ -3,19 +3,15 @@ import { AuthEvents, User } from './types'; import { withMetricsAsync } from './utils/metrics'; import { decodeJwtPayload } from './utils/jwt'; -const trackFlowMock = jest.fn(); -const trackErrorMock = jest.fn(); -const identifyMock = jest.fn(); const trackMock = jest.fn(); -const getDetailMock = jest.fn(); +const getRuntimeIdMock = jest.fn(); jest.mock('@imtbl/metrics', () => ({ - Detail: { RUNTIME_ID: 'runtime-id' }, - trackFlow: (...args: any[]) => trackFlowMock(...args), - trackError: (...args: any[]) => trackErrorMock(...args), - identify: (...args: any[]) => identifyMock(...args), track: (...args: any[]) => trackMock(...args), - getDetail: (...args: any[]) => getDetailMock(...args), +})); + +jest.mock('./utils/runtimeId', () => ({ + getRuntimeId: (...args: any[]) => getRuntimeIdMock(...args), })); jest.mock('./utils/jwt', () => ({ @@ -23,58 +19,42 @@ jest.mock('./utils/jwt', () => ({ })); beforeEach(() => { - trackFlowMock.mockReset(); - trackErrorMock.mockReset(); - identifyMock.mockReset(); trackMock.mockReset(); - getDetailMock.mockReset(); + getRuntimeIdMock.mockReset(); (decodeJwtPayload as jest.Mock).mockReset(); }); describe('withMetricsAsync', () => { - it('resolves with function result and tracks flow', async () => { - const flow = { - addEvent: jest.fn(), - details: { flowId: 'flow-id' }, - }; - trackFlowMock.mockReturnValue(flow); - + it('resolves with function result and tracks method', async () => { const result = await withMetricsAsync(async () => 'done', 'login'); expect(result).toEqual('done'); - expect(trackFlowMock).toHaveBeenCalledWith('passport', 'login', true); - expect(flow.addEvent).toHaveBeenCalledWith('End'); + expect(trackMock).toHaveBeenCalledWith('passport', 'login'); }); it('tracks error when function throws', async () => { - const flow = { - addEvent: jest.fn(), - details: { flowId: 'flow-id' }, - }; - trackFlowMock.mockReturnValue(flow); const error = new Error('boom'); await expect(withMetricsAsync(async () => { throw error; }, 'login')).rejects.toThrow(error); - expect(trackErrorMock).toHaveBeenCalledWith('passport', 'login', error, { flowId: 'flow-id' }); - expect(flow.addEvent).toHaveBeenCalledWith('End'); + expect(trackMock).toHaveBeenCalledWith('passport', 'login'); + expect(trackMock).toHaveBeenCalledWith('passport', 'login', { error }); }); - it('does not fail when non-error is thrown', async () => { - const flow = { - addEvent: jest.fn(), - details: { flowId: 'flow-id' }, - }; - trackFlowMock.mockReturnValue(flow); - + it('does not track error payload when non-error is thrown', async () => { const nonError = { message: 'failure' }; await expect(withMetricsAsync(async () => { throw nonError as unknown as Error; }, 'login')).rejects.toBe(nonError); - expect(flow.addEvent).toHaveBeenCalledWith('errored'); + expect(trackMock).toHaveBeenCalledWith('passport', 'login'); + expect(trackMock).not.toHaveBeenCalledWith( + 'passport', + 'login', + expect.objectContaining({ error: expect.anything() }), + ); }); }); @@ -104,7 +84,6 @@ describe('Auth', () => { expect(loginWithPopup).toHaveBeenCalledTimes(1); expect((auth as any).eventEmitter.emit).toHaveBeenCalledWith(AuthEvents.LOGGED_IN, user); - expect(identifyMock).toHaveBeenCalledWith({ passportId: user.profile.sub }); }); it('returns cached user without triggering login', async () => { @@ -120,7 +99,6 @@ describe('Auth', () => { expect(user).toBe(cachedUser); expect((auth as any).loginWithPopup).not.toHaveBeenCalled(); expect((auth as any).eventEmitter.emit).not.toHaveBeenCalled(); - expect(identifyMock).not.toHaveBeenCalled(); }); }); @@ -128,7 +106,7 @@ describe('Auth', () => { it('omits third_party_a_id when no anonymous id is provided', () => { const auth = Object.create(Auth.prototype) as Auth; (auth as any).userManager = { settings: { extraQueryParams: {} } }; - getDetailMock.mockReturnValue('runtime-id-value'); + getRuntimeIdMock.mockReturnValue('runtime-id-value'); const params = (auth as any).buildExtraQueryParams(); @@ -454,7 +432,7 @@ describe('Auth', () => { (auth as any).config = { popupOverlayOptions: { disableHeadlessLoginPromptOverlay: true }, }; - getDetailMock.mockReturnValue('runtime-id-value'); + getRuntimeIdMock.mockReturnValue('runtime-id-value'); const user = await (auth as any).loginWithPopup({ directLoginMethod: 'google', @@ -487,7 +465,7 @@ describe('Auth', () => { (auth as any).config = { popupOverlayOptions: { disableHeadlessLoginPromptOverlay: true }, }; - getDetailMock.mockReturnValue('runtime-id-value'); + getRuntimeIdMock.mockReturnValue('runtime-id-value'); await (auth as any).loginWithPopup({ directLoginMethod: 'google', @@ -513,7 +491,7 @@ describe('Auth', () => { (auth as any).config = { popupOverlayOptions: { disableHeadlessLoginPromptOverlay: true }, }; - getDetailMock.mockReturnValue('runtime-id-value'); + getRuntimeIdMock.mockReturnValue('runtime-id-value'); await expect((auth as any).loginWithPopup({ directLoginMethod: 'google', diff --git a/packages/auth/src/Auth.ts b/packages/auth/src/Auth.ts index f4050986e1..751b0ea40c 100644 --- a/packages/auth/src/Auth.ts +++ b/packages/auth/src/Auth.ts @@ -8,13 +8,8 @@ import { WebStorageStateStore, } from 'oidc-client-ts'; import localForage from 'localforage'; -import { - Detail, - getDetail, - identify, - track, - trackError, -} from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; +import { getRuntimeId } from './utils/runtimeId'; import { AuthConfiguration, IAuthConfiguration } from './config'; import { AuthModuleConfiguration, @@ -208,7 +203,7 @@ export class Auth { user = await this.getUserInternal(); } catch (error: any) { if (error instanceof Error && !error.message.includes('Unknown or invalid refresh token')) { - trackError('passport', 'login', error); + track('passport', 'login', { error }); } if (useCachedSession) { throw error; @@ -227,7 +222,7 @@ export class Auth { user = await this.loginWithPopup(options?.directLoginOptions); } - // Emit LOGGED_IN event and identify user if logged in + // Emit LOGGED_IN event if logged in if (user) { this.handleSuccessfulLogin(user); } @@ -317,7 +312,7 @@ export class Auth { return withMetricsAsync(async () => { const user = await this.getUserInternal(); return user?.idToken; - }, 'getIdToken', false); + }, 'getIdToken'); } /** @@ -328,7 +323,7 @@ export class Auth { return withMetricsAsync(async () => { const user = await this.getUserInternal(); return user?.accessToken; - }, 'getAccessToken', false, false); + }, 'getAccessToken'); } /** @@ -436,7 +431,6 @@ export class Auth { private handleSuccessfulLogin(user: User): void { this.eventEmitter.emit(AuthEvents.LOGGED_IN, user); - identify({ passportId: user.profile.sub }); } private buildExtraQueryParams( @@ -445,7 +439,7 @@ export class Auth { ): Record { const params: Record = { ...(this.userManager.settings?.extraQueryParams ?? {}), - rid: getDetail(Detail.RUNTIME_ID) || '', + rid: getRuntimeId(), }; if (directLoginOptions) { diff --git a/packages/auth/src/login/embeddedLoginPrompt.ts b/packages/auth/src/login/embeddedLoginPrompt.ts index 50b384c7dc..ea51b63813 100644 --- a/packages/auth/src/login/embeddedLoginPrompt.ts +++ b/packages/auth/src/login/embeddedLoginPrompt.ts @@ -1,4 +1,3 @@ -import { Detail, getDetail } from '@imtbl/metrics'; import { EMBEDDED_LOGIN_PROMPT_EVENT_TYPE, EmbeddedLoginPromptResult, @@ -6,6 +5,7 @@ import { } from './types'; import { IAuthConfiguration } from '../config'; import EmbeddedLoginPromptOverlay from '../overlay/embeddedLoginPromptOverlay'; +import { getRuntimeId } from '../utils/runtimeId'; const LOGIN_PROMPT_WINDOW_HEIGHT = 660; const LOGIN_PROMPT_WINDOW_WIDTH = 440; @@ -23,7 +23,7 @@ export default class EmbeddedLoginPrompt { private getHref = () => { const href = `${this.config.authenticationDomain}/im-embedded-login-prompt` + `?client_id=${this.config.oidcConfiguration.clientId}` - + `&rid=${getDetail(Detail.RUNTIME_ID)}`; + + `&rid=${getRuntimeId()}`; return href; }; diff --git a/packages/auth/src/login/standalone.ts b/packages/auth/src/login/standalone.ts index 8a23a3bb4b..ee0b2e47ec 100644 --- a/packages/auth/src/login/standalone.ts +++ b/packages/auth/src/login/standalone.ts @@ -4,8 +4,9 @@ * making them ideal for use with external session managers like NextAuth. */ -import { Detail, getDetail, track } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import { decodeJwtPayload } from '../utils/jwt'; +import { getRuntimeId } from '../utils/runtimeId'; import type { DirectLoginOptions, IdTokenPayload, MarketingConsentStatus, ZkEvmInfo, } from '../types'; @@ -259,7 +260,7 @@ function appendEmbeddedLoginPromptStyles(): void { } function createEmbeddedLoginIFrame(authDomain: string, clientId: string): HTMLIFrameElement { - const runtimeId = getDetail(Detail.RUNTIME_ID); + const runtimeId = getRuntimeId(); const iframe = document.createElement('iframe'); iframe.id = LOGIN_PROMPT_IFRAME_ID; iframe.src = `${authDomain}/im-embedded-login-prompt?client_id=${clientId}&rid=${runtimeId}`; diff --git a/packages/auth/src/utils/metrics.ts b/packages/auth/src/utils/metrics.ts index 683c9ac717..b9aeecc9ad 100644 --- a/packages/auth/src/utils/metrics.ts +++ b/packages/auth/src/utils/metrics.ts @@ -1,29 +1,17 @@ -import { Flow, trackError, trackFlow } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; export const withMetricsAsync = async ( - fn: (flow: Flow) => Promise, + fn: () => Promise, flowName: string, - trackStartEvent: boolean = true, - trackEndEvent: boolean = true, ): Promise => { - const flow: Flow = trackFlow( - 'passport', - flowName, - trackStartEvent, - ); + track('passport', flowName); try { - return await fn(flow); + return await fn(); } catch (error) { if (error instanceof Error) { - trackError('passport', flowName, error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', flowName, { error }); } throw error; - } finally { - if (trackEndEvent) { - flow.addEvent('End'); - } } }; diff --git a/packages/auth/src/utils/runtimeId.ts b/packages/auth/src/utils/runtimeId.ts new file mode 100644 index 0000000000..094090531e --- /dev/null +++ b/packages/auth/src/utils/runtimeId.ts @@ -0,0 +1,51 @@ +/** + * Opaque runtime id for auth `rid` query params. + * Owned by auth so login URLs do not depend on metrics. + */ + +const STORAGE_KEY = '__IMX-auth-runtime-id'; +const LEGACY_METRICS_RUNTIME_KEY = '__IMX-metrics-runtime'; + +const generateId = () => { + const s4 = () => Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; +}; + +const readLegacyMetricsRuntimeId = (): string | undefined => { + if (typeof window === 'undefined' || !window.localStorage) { + return undefined; + } + try { + const raw = window.localStorage.getItem(LEGACY_METRICS_RUNTIME_KEY); + if (!raw) { + return undefined; + } + const parsed = JSON.parse(raw) as { rid?: string }; + return typeof parsed.rid === 'string' && parsed.rid.length > 0 + ? parsed.rid + : undefined; + } catch { + return undefined; + } +}; + +/** + * Returns a stable opaque runtime id for this browser profile. + * Migrates from the former metrics-owned rid when present. + */ +export const getRuntimeId = (): string => { + if (typeof window === 'undefined' || !window.localStorage) { + return generateId(); + } + + const existing = window.localStorage.getItem(STORAGE_KEY); + if (existing) { + return existing; + } + + const migrated = readLegacyMetricsRuntimeId() ?? generateId(); + window.localStorage.setItem(STORAGE_KEY, migrated); + return migrated; +}; diff --git a/packages/checkout/sdk/package.json b/packages/checkout/sdk/package.json index 962fcaa731..07342df6b5 100644 --- a/packages/checkout/sdk/package.json +++ b/packages/checkout/sdk/package.json @@ -10,7 +10,6 @@ "@imtbl/config": "workspace:*", "@imtbl/dex-sdk": "workspace:*", "@imtbl/generated-clients": "workspace:*", - "@imtbl/metrics": "workspace:*", "@imtbl/orderbook": "workspace:*", "@imtbl/passport": "workspace:*", "@metamask/detect-provider": "^2.0.0", diff --git a/packages/checkout/sdk/src/smartCheckout/buy/buy.ts b/packages/checkout/sdk/src/smartCheckout/buy/buy.ts index 1cf1bb446f..2c9984797f 100644 --- a/packages/checkout/sdk/src/smartCheckout/buy/buy.ts +++ b/packages/checkout/sdk/src/smartCheckout/buy/buy.ts @@ -9,7 +9,6 @@ import { OrderStatusName, } from '@imtbl/orderbook'; import { mr } from '@imtbl/generated-clients'; -import { track } from '@imtbl/metrics'; import { TransactionRequest, TransactionResponse } from 'ethers'; import * as instance from '../../instance'; import { CheckoutConfiguration, getL1ChainId, getL2ChainId } from '../../config'; @@ -94,8 +93,6 @@ export const buy = async ( waitFulfillmentSettlements: true, }, ): Promise => { - track('checkout_sdk', 'buy_initiated'); - if (orders.length === 0) { throw new CheckoutError( 'No orders were provided to the orders array. Please provide at least one order.', diff --git a/packages/checkout/sdk/src/smartCheckout/sell/sell.ts b/packages/checkout/sdk/src/smartCheckout/sell/sell.ts index e8dbef2cb8..78c02fe527 100644 --- a/packages/checkout/sdk/src/smartCheckout/sell/sell.ts +++ b/packages/checkout/sdk/src/smartCheckout/sell/sell.ts @@ -9,7 +9,6 @@ import { ERC721Item as OrderbookERC721Item, ERC1155Item as OrderbookERC1155Item, } from '@imtbl/orderbook'; -import { track } from '@imtbl/metrics'; import { ERC721Item, ERC1155Item, @@ -93,8 +92,6 @@ export const sell = async ( let listing: PrepareListingResponse; let spenderAddress = ''; - track('checkout_sdk', 'sell_initiated'); - if (orders.length === 0) { throw new CheckoutError( 'No orders were provided to the orders array. Please provide at least one order.', diff --git a/packages/checkout/widgets-lib/src/lib/metrics.ts b/packages/checkout/widgets-lib/src/lib/metrics.ts deleted file mode 100644 index 58ebca969e..0000000000 --- a/packages/checkout/widgets-lib/src/lib/metrics.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Flow, trackError, trackFlow } from '@imtbl/metrics'; - -export const withMetrics = ( - fn: (flow: Flow) => T, - flowName: string, -): T => { - const flow: Flow = trackFlow('commerce', flowName); - - try { - return fn(flow); - } catch (error) { - if (error instanceof Error) { - trackError('commerce', flowName, error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); - } - throw error; - } finally { - flow.addEvent('End'); - } -}; - -export const withMetricsAsync = async ( - fn: (flow: Flow) => Promise, - flowName: string, - anonymousId?: string, - errorType?: (error:any)=>string, -): Promise => { - const flow: Flow = trackFlow('commerce', flowName); - if (anonymousId) { - flow.addEvent(`anonymousId_${anonymousId}`); - } - try { - return await fn(flow); - } catch (error:any) { - if (error instanceof Error) { - trackError('commerce', flowName, error, { flowId: flow.details.flowId }); - } - if (errorType && errorType(error)) { - flow.addEvent(`errored_${errorType(error)}`); - } else { - flow.addEvent('errored'); - } - throw error; - } finally { - flow.addEvent('End'); - } -}; diff --git a/packages/checkout/widgets-lib/src/lib/squid/functions/execute.ts b/packages/checkout/widgets-lib/src/lib/squid/functions/execute.ts index 8686e809a4..72cef20e6a 100644 --- a/packages/checkout/widgets-lib/src/lib/squid/functions/execute.ts +++ b/packages/checkout/widgets-lib/src/lib/squid/functions/execute.ts @@ -1,5 +1,4 @@ import { EIP6963ProviderInfo, WrappedBrowserProvider } from '@imtbl/checkout-sdk'; -import { Flow } from '@imtbl/metrics'; import { ethers, TransactionReceipt, TransactionResponse } from 'ethers'; import { RouteResponse } from '@0xsquid/squid-types'; import { Squid } from '@0xsquid/sdk'; @@ -39,12 +38,10 @@ export const waitForReceipt = async ( }; export const callApprove = async ( - flow: Flow, - fromProviderInfo: EIP6963ProviderInfo, + _fromProviderInfo: EIP6963ProviderInfo, provider: WrappedBrowserProvider, routeResponse: RouteResponse, ): Promise => { - flow.addEvent(`provider_${fromProviderInfo.name}`); const erc20Abi = [ 'function approve(address spender, uint256 amount) public returns (bool)', ]; @@ -66,23 +63,19 @@ export const callApprove = async ( transactionRequestTarget, fromAmount, ); - flow.addEvent('transactionSent'); return await waitForReceipt(provider, tx.hash); }; export const callExecute = async ( - flow: Flow, squid: Squid, - fromProviderInfo: EIP6963ProviderInfo, + _fromProviderInfo: EIP6963ProviderInfo, provider: WrappedBrowserProvider, routeResponse: RouteResponse, ): Promise => { - flow.addEvent(`provider_${fromProviderInfo.name}`); const tx = (await squid.executeRoute({ signer: await provider.getSigner() as unknown as EvmWallet, route: routeResponse.route, })) as unknown as TransactionResponse; - flow.addEvent('transactionSent'); return await waitForReceipt(provider, tx.hash); }; diff --git a/packages/checkout/widgets-lib/src/lib/squid/hooks/useExecute.ts b/packages/checkout/widgets-lib/src/lib/squid/hooks/useExecute.ts index 791055cca2..8e3cc356a2 100644 --- a/packages/checkout/widgets-lib/src/lib/squid/hooks/useExecute.ts +++ b/packages/checkout/widgets-lib/src/lib/squid/hooks/useExecute.ts @@ -8,19 +8,16 @@ import { StatusResponse } from '@0xsquid/sdk/dist/types'; import { EIP6963ProviderInfo, WrappedBrowserProvider } from '@imtbl/checkout-sdk'; import { isSquidNativeToken } from '../functions/isSquidNativeToken'; import { retry } from '../../retry'; -import { withMetricsAsync } from '../../metrics'; -import { useAnalytics, UserJourney } from '../../../context/analytics-provider/SegmentAnalyticsProvider'; -import { isRejectedError } from '../../../functions/errorType'; +import { UserJourney } from '../../../context/analytics-provider/SegmentAnalyticsProvider'; import { callApprove, callExecute } from '../functions/execute'; const TRANSACTION_NOT_COMPLETED = 'transaction not completed'; export const useExecute = ( - userJourney: UserJourney, + // Kept for call-site compatibility; metrics wrapping was removed. + _userJourney: UserJourney, onTransactionError?: (err: unknown) => void, ) => { - const { user } = useAnalytics(); - const getAllowance = async ( provider: WrappedBrowserProvider, routeResponse: RouteResponse, @@ -54,15 +51,6 @@ export const useExecute = ( } }; - const getAnonymousId = async () => { - try { - const userData = await user(); - return userData?.anonymousId() ?? undefined; - } catch (error) { - return undefined; - } - }; - const approve = async ( fromProviderInfo: EIP6963ProviderInfo, provider: WrappedBrowserProvider, @@ -70,12 +58,7 @@ export const useExecute = ( ): Promise => { try { if (!isSquidNativeToken(routeResponse?.route?.params.fromToken)) { - return await withMetricsAsync( - (flow) => callApprove(flow, fromProviderInfo, provider, routeResponse), - `${userJourney}_Approve`, - await getAnonymousId(), - (error) => (isRejectedError(error) ? 'rejected' : ''), - ); + return await callApprove(fromProviderInfo, provider, routeResponse); } return undefined; } catch (error) { @@ -94,12 +77,7 @@ export const useExecute = ( throw new Error('provider does not have send method'); } try { - return await withMetricsAsync( - (flow) => callExecute(flow, squid, fromProviderInfo, provider, routeResponse), - `${userJourney}_Execute`, - await getAnonymousId(), - (error) => (isRejectedError(error) ? 'rejected' : ''), - ); + return await callExecute(squid, fromProviderInfo, provider, routeResponse); } catch (error) { onTransactionError?.(error); return undefined; diff --git a/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx b/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx index dfc58616ff..01526ca66f 100644 --- a/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx +++ b/packages/checkout/widgets-lib/src/widgets/add-tokens/views/AddTokens.tsx @@ -29,7 +29,6 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { ActionType } from '@0xsquid/squid-types'; -import { trackFlow } from '@imtbl/metrics'; import { parseUnits } from 'ethers/utils'; import { SimpleLayout } from '../../../components/SimpleLayout/SimpleLayout'; import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext'; @@ -307,8 +306,6 @@ export function AddTokens({ toTokenAddress, geoBlocked: !isSwapAvailable, }, - }).then((ctx) => { - trackFlow('commerce', `addTokensLoaded_${ctx.event.messageId}`); }); }, [id, isSwapAvailable]); diff --git a/packages/checkout/widgets-lib/src/widgets/add-tokens/views/Review.tsx b/packages/checkout/widgets-lib/src/widgets/add-tokens/views/Review.tsx index ea92233ecc..296a6ff51a 100644 --- a/packages/checkout/widgets-lib/src/widgets/add-tokens/views/Review.tsx +++ b/packages/checkout/widgets-lib/src/widgets/add-tokens/views/Review.tsx @@ -15,7 +15,6 @@ import { useInterval, } from '@biom3/react'; import { ChainId } from '@imtbl/checkout-sdk'; -import { trackFlow } from '@imtbl/metrics'; import { t } from 'i18next'; import { useCallback, @@ -359,8 +358,6 @@ export function Review({ fromTokenSymbol: amountData?.fromToken.symbol, toTokenSymbol: amountData?.toToken.symbol, }, - }).then((ctx) => { - trackFlow('commerce', `addTokensFundsAdded_${ctx.event.messageId}`); }); sendAddTokensSuccessEvent(eventTarget, executeTxnReceipt.hash); diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx index 1d2f08e410..6221e1658b 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx @@ -13,7 +13,6 @@ import { } from 'react'; import { useTranslation } from 'react-i18next'; import { Environment } from '@imtbl/config'; -import { trackError } from '@imtbl/metrics'; import { UserJourney, useAnalytics } from '../../../context/analytics-provider/SegmentAnalyticsProvider'; import { amountInputValidation } from '../../../lib/validations/amountInputValidations'; import { BridgeActions, BridgeContext } from '../context/BridgeContext'; @@ -276,8 +275,8 @@ export function BridgeForm(props: BridgeFormProps) { // Use child token address if mapping exists, otherwise use original token address tokenAddress = tokenMapping.childToken; - } catch (error) { - trackError('commerce', 'bridgeForm', error instanceof Error ? error : new Error(String(error))); + } catch { + // Token mapping lookup failed; fall through with original token address. } } diff --git a/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx b/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx index c8c4e4d7f1..8a2c79b24c 100644 --- a/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx +++ b/packages/checkout/widgets-lib/src/widgets/sale/context/SaleContextProvider.tsx @@ -14,7 +14,6 @@ import { useState, } from 'react'; import { Environment } from '@imtbl/config'; -import { trackError } from '@imtbl/metrics'; import { ConnectLoaderState } from '../../../context/connect-loader-context/ConnectLoaderContext'; import { SaleWidgetViews } from '../../../context/view-context/SaleViewContextTypes'; import { @@ -294,12 +293,6 @@ export function SaleContextProvider(props: { setPaymentMethod(undefined); } - const { vendorError, ...errorData } = data; - trackError('commerce', 'saleError', new Error(errorType), { - ...errorData, - ...(vendorError ? { vendorCode: vendorError.code, vendorMessage: vendorError.message || '' } : {}), - }); - // eslint-disable-next-line no-console console.error('[IMTBL]: Sale error', errorType, data); diff --git a/packages/config/package.json b/packages/config/package.json index c2c9595328..8b46d87693 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -4,9 +4,7 @@ "version": "0.0.0", "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", - "dependencies": { - "@imtbl/metrics": "workspace:*" - }, + "dependencies": {}, "devDependencies": { "@swc/core": "^1.4.2", "@swc/jest": "^0.2.37", diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index ae739c8978..614d7d8db5 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,5 +1,3 @@ -import { track, setEnvironment, setPublishableApiKey } from '@imtbl/metrics'; - export enum Environment { PRODUCTION = 'production', SANDBOX = 'sandbox', @@ -30,9 +28,6 @@ export class ImmutableConfiguration { this.publishableKey = options.publishableKey; this.apiKey = options.apiKey; this.rateLimitingKey = options.rateLimitingKey; - - setEnvironment(options.environment); - track('config', 'created_imtbl_config'); } } @@ -54,7 +49,6 @@ export const addKeysToHeadersOverride = { try { passportClient = new passport.Passport(passportConfig); - trackDuration(moduleName, 'initialisedPassport', mt(markStart)); + track(moduleName, 'initialisedPassport', { durationMs: mt(markStart) }); } catch (initError) { console.error('[GAME-BRIDGE] Error creating Passport client:', initError); throw initError; @@ -274,9 +269,7 @@ window.callFunction = async (jsonData: string) => { }; console.log(`Version check: ${JSON.stringify(versionInfo)}`); - trackDuration(moduleName, 'completedInitGameBridge', mt(markStart), { - ...versionInfo, - }); + track(moduleName, 'completedInitGameBridge', { durationMs: mt(markStart) }); break; } case PASSPORT_FUNCTIONS.relogin: { @@ -289,10 +282,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to re-login'); } - identify({ passportId: userInfo?.sub }); - trackDuration(moduleName, 'performedRelogin', mt(markStart), { - succeeded, - }); + track(moduleName, 'performedRelogin', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -306,7 +296,7 @@ window.callFunction = async (jsonData: string) => { const directLoginOptions: passport.DirectLoginOptions | undefined = request?.directLoginOptions; const imPassportTraceId: string | undefined = request?.imPassportTraceId; const url = await getPassportClient().loginWithPKCEFlow(directLoginOptions, imPassportTraceId); - trackDuration(moduleName, 'performedGetPkceAuthUrl', mt(markStart)); + track(moduleName, 'performedGetPkceAuthUrl', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -318,12 +308,11 @@ window.callFunction = async (jsonData: string) => { } case PASSPORT_FUNCTIONS.loginPKCE: { const request = JSON.parse(data); - const profile = await getPassportClient().loginWithPKCEFlowCallback( + await getPassportClient().loginWithPKCEFlowCallback( request.authorizationCode, request.state, ); - identify({ passportId: profile.sub }); - trackDuration(moduleName, 'performedLoginPkce', mt(markStart)); + track(moduleName, 'performedLoginPkce', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -335,7 +324,7 @@ window.callFunction = async (jsonData: string) => { case PASSPORT_FUNCTIONS.logout: { const logoutUrl = await getPassportClient().getLogoutUrl(); zkEvmProviderInstance = null; - trackDuration(moduleName, 'performedGetLogoutUrl', mt(markStart)); + track(moduleName, 'performedGetLogoutUrl', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -353,7 +342,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('No access token'); } - trackDuration(moduleName, 'performedGetAccessToken', mt(markStart)); + track(moduleName, 'performedGetAccessToken', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -371,7 +360,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('No ID token'); } - trackDuration(moduleName, 'performedGetIdToken', mt(markStart)); + track(moduleName, 'performedGetIdToken', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -389,7 +378,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('No email'); } - trackDuration(moduleName, 'performedGetEmail', mt(markStart)); + track(moduleName, 'performedGetEmail', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -407,7 +396,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('No Passport ID'); } - trackDuration(moduleName, 'performedGetPassportId', mt(markStart)); + track(moduleName, 'performedGetPassportId', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -419,7 +408,7 @@ window.callFunction = async (jsonData: string) => { } case PASSPORT_FUNCTIONS.getLinkedAddresses: { const linkedAddresses = await getPassportClient().getLinkedAddresses(); - trackDuration(moduleName, 'performedGetLinkedAddresses', mt(markStart)); + track(moduleName, 'performedGetLinkedAddresses', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -432,7 +421,7 @@ window.callFunction = async (jsonData: string) => { case PASSPORT_FUNCTIONS.storeTokens: { const tokenResponse = JSON.parse(data); const response = await getPassportClient().storeTokens(tokenResponse); - trackDuration(moduleName, 'performedStoreTokens', mt(markStart)); + track(moduleName, 'performedStoreTokens', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -450,9 +439,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to connect to EVM'); } - trackDuration(moduleName, 'performedZkevmConnectEvm', mt(markStart), { - succeeded: providerSet, - }); + track(moduleName, 'performedZkevmConnectEvm', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -473,11 +460,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to send transaction'); } - trackDuration(moduleName, 'performedZkevmSendTransaction', mt(markStart), { - requestId, - transactionRequest: JSON.stringify(transaction), - transactionResponse: transactionHash, - }); + track(moduleName, 'performedZkevmSendTransaction', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -495,11 +478,7 @@ window.callFunction = async (jsonData: string) => { const tx = await signer.sendTransaction(transaction); const response = await tx.wait(); - trackDuration(moduleName, 'performedZkevmSendTransactionWithConfirmation', mt(markStart), { - requestId, - transactionRequest: JSON.stringify(transaction), - transactionResponse: JSON.stringify(response?.toJSON()), - }); + track(moduleName, 'performedZkevmSendTransactionWithConfirmation', { durationMs: mt(markStart) }); callbackToGame({ ...{ responseFor: fxName, @@ -527,9 +506,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to sign payload'); } - trackDuration(moduleName, 'performedZkevmSignTypedDataV4', mt(markStart), { - requestId, - }); + track(moduleName, 'performedZkevmSignTypedDataV4', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -549,7 +526,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to request accounts'); } - trackDuration(moduleName, 'performedZkevmRequestAccounts', mt(markStart)); + track(moduleName, 'performedZkevmRequestAccounts', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -571,7 +548,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to get balance'); } - trackDuration(moduleName, 'performedZkevmGetBalance', mt(markStart)); + track(moduleName, 'performedZkevmGetBalance', { durationMs: mt(markStart) }); callbackToGame({ responseFor: fxName, requestId, @@ -593,7 +570,7 @@ window.callFunction = async (jsonData: string) => { throw new Error('Failed to get transaction receipt'); } - trackDuration(moduleName, 'performedZkevmGetTransactionReceipt', mt(markStart)); + track(moduleName, 'performedZkevmGetTransactionReceipt', { durationMs: mt(markStart) }); callbackToGame({ ...{ responseFor: fxName, @@ -647,17 +624,8 @@ window.callFunction = async (jsonData: string) => { ? error?.type : undefined; - trackError(moduleName, fxName, wrappedError, { - fxName, - requestId, - errorType, - ...versionInfo, - }); - trackDuration(moduleName, 'failedCallFunction', mt(markStart), { - fxName, - requestId, - error: wrappedError.message, - }); + track(moduleName, fxName, { error: wrappedError }); + track(moduleName, 'failedCallFunction', { durationMs: mt(markStart) }); console.log('callFunction error', wrappedError); console.log('callFunction errorType', errorType); diff --git a/packages/internal/metrics/README.md b/packages/internal/metrics/README.md index 403240a7bd..8bf6cdb6cf 100644 --- a/packages/internal/metrics/README.md +++ b/packages/internal/metrics/README.md @@ -1,3 +1,30 @@ -# About +# @imtbl/metrics -This internal package is used by the Typescript Immutable SDK package `@imtbl/sdk` and its children SDK packages. \ No newline at end of file +Best-effort SDK ops telemetry: **which SDK version, which method, optional error code**. + +Not user analytics. No identity, stacks, device fingerprints, env, or persistent queues. + +## API + +```ts +import { configure, track } from '@imtbl/metrics'; + +configure({ clientId: 'your-passport-client-id' }); + +track('passport', 'login'); +track('passport', 'login', { durationMs: 120 }); + +try { + await doWork(); +} catch (error) { + if (error instanceof Error) { + track('passport', 'login', { error }); + } + throw error; +} +``` + +Telemetry disables for the page session after the first failed or retired (204) server response. + +Internally, events are mapped to the existing `sdk-analytics` v1 wire format so older and +newer SDK clients share the same backend contract. diff --git a/packages/internal/metrics/package.json b/packages/internal/metrics/package.json index 3f2863e37c..fe9df9b697 100644 --- a/packages/internal/metrics/package.json +++ b/packages/internal/metrics/package.json @@ -4,10 +4,7 @@ "version": "0.0.0", "author": "Immutable", "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", - "dependencies": { - "global-const": "^0.1.2", - "lru-memorise": "0.3.0" - }, + "dependencies": {}, "devDependencies": { "@swc/core": "^1.4.2", "@swc/jest": "^0.2.37", diff --git a/packages/internal/metrics/src/client.ts b/packages/internal/metrics/src/client.ts new file mode 100644 index 0000000000..2764f1063e --- /dev/null +++ b/packages/internal/metrics/src/client.ts @@ -0,0 +1,49 @@ +import { generateRuntimeId } from './runtimeId'; +import type { MetricsConfig } from './types'; + +// WARNING: DO NOT CHANGE THE STRING BELOW. IT GETS REPLACED AT BUILD TIME. +const SDK_VERSION = '__SDK_VERSION__'; + +type RuntimeState = { + sdkVersion: string; + clientId?: string; + /** Opaque session id required by legacy sdk-analytics v1 wire format. */ + runtimeId: string; + enabled: boolean; +}; + +const state: RuntimeState = { + sdkVersion: SDK_VERSION, + runtimeId: generateRuntimeId(), + enabled: true, +}; + +/** + * Configure app-level metrics context. + * Partial updates merge with existing config. + */ +export const configure = (config: MetricsConfig): void => { + if (config.clientId !== undefined) { + state.clientId = config.clientId; + } +}; + +export const getSdkVersion = (): string => state.sdkVersion; + +export const getClientId = (): string | undefined => state.clientId; + +export const getRuntimeId = (): string => state.runtimeId; + +export const isTelemetryEnabled = (): boolean => state.enabled; + +export const disableTelemetry = (): void => { + state.enabled = false; +}; + +/** Reset for unit tests only. */ +export const resetClientForTests = (): void => { + state.sdkVersion = SDK_VERSION; + state.clientId = undefined; + state.runtimeId = generateRuntimeId(); + state.enabled = true; +}; diff --git a/packages/internal/metrics/src/details.ts b/packages/internal/metrics/src/details.ts deleted file mode 100644 index e6e57c547b..0000000000 --- a/packages/internal/metrics/src/details.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { errorBoundary } from './utils/errorBoundary'; -import { Detail } from './utils/constants'; -import { storeDetail, getDetail as getDetailFn } from './utils/state'; -import { getGlobalisedValue } from './utils/globalise'; - -const setEnvironmentFn = (env: 'sandbox' | 'production') => { - storeDetail(Detail.ENVIRONMENT, env); -}; -export const setEnvironment = errorBoundary( - getGlobalisedValue('setEnvironment', setEnvironmentFn), -); - -const setPassportClientIdFn = (passportClientId: string) => { - storeDetail(Detail.PASSPORT_CLIENT_ID, passportClientId); -}; -export const setPassportClientId = errorBoundary( - getGlobalisedValue('setPassportClientId', setPassportClientIdFn), -); - -const setPublishableApiKeyFn = (publishableApiKey: string) => { - storeDetail(Detail.PUBLISHABLE_API_KEY, publishableApiKey); -}; -export const setPublishableApiKey = errorBoundary( - getGlobalisedValue('setPublishableApiKey', setPublishableApiKeyFn), -); - -export const getDetail = errorBoundary( - getGlobalisedValue('getDetail', getDetailFn), -); - -export { Detail }; diff --git a/packages/internal/metrics/src/emit.test.ts b/packages/internal/metrics/src/emit.test.ts new file mode 100644 index 0000000000..47da6a2951 --- /dev/null +++ b/packages/internal/metrics/src/emit.test.ts @@ -0,0 +1,113 @@ +import { + configure, + getRuntimeId, + isTelemetryEnabled, + resetClientForTests, +} from './client'; +import { track } from './emit'; +import { resetTransportForTests } from './transport'; + +const flushSends = async () => { + await Promise.resolve(); + await Promise.resolve(); + await new Promise((resolve) => { + setTimeout(resolve, 0); + }); +}; + +const decodePayload = (fetchMock: jest.Mock) => { + const [, options] = fetchMock.mock.calls[0]; + const body = JSON.parse(options.body); + return JSON.parse(Buffer.from(body.payload, 'base64').toString('utf-8')); +}; + +describe('@imtbl/metrics', () => { + const fetchMock = jest.fn(); + + beforeEach(() => { + resetClientForTests(); + resetTransportForTests(); + fetchMock.mockReset(); + global.fetch = fetchMock; + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + text: async () => '', + }); + }); + + it('maps the slim track API to the legacy v1 wire envelope', async () => { + configure({ clientId: 'client-123' }); + track('passport', 'login'); + await flushSends(); + + const payload = decodePayload(fetchMock); + expect(payload).toEqual({ + version: 1, + data: { + details: { + rid: getRuntimeId(), + sdkVersion: expect.any(String), + passportClientId: 'client-123', + }, + events: [{ + event: 'passport.login', + time: expect.any(String), + }], + }, + }); + expect(payload.data.details.env).toBeUndefined(); + expect(payload.data.details.uid).toBeUndefined(); + expect(Number.isNaN(Date.parse(payload.data.events[0].time))).toBe(false); + }); + + it('maps durationMs and redacted error onto v1 properties', async () => { + const error = Object.assign(new Error('secret'), { + name: 'PassportError', + type: 'AUTHENTICATION_ERROR', + stack: 'Error: secret\n at foo', + }); + track('passport', 'login', { durationMs: 15.7, error }); + await flushSends(); + + const payload = decodePayload(fetchMock); + expect(payload.data.events[0]).toEqual({ + event: 'passport.trackError_login', + time: expect.any(String), + properties: [ + ['durationMs', '16'], + ['isTrackError', 'true'], + ['errorName', 'PassportError'], + ['errorCode', 'AUTHENTICATION_ERROR'], + ], + }); + expect(JSON.stringify(payload)).not.toContain('secret'); + }); + + it('does not drop duplicate events', async () => { + track('passport', 'login'); + track('passport', 'login'); + await flushSends(); + + expect(decodePayload(fetchMock).data.events).toHaveLength(2); + }); + + it('disables after 5xx and makes later track a no-op', async () => { + fetchMock.mockResolvedValueOnce({ ok: false, status: 503, statusText: 'down' }); + track('passport', 'a'); + await flushSends(); + + expect(isTelemetryEnabled()).toBe(false); + track('passport', 'b'); + await flushSends(); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + it('disables on 204 retirement', async () => { + fetchMock.mockResolvedValueOnce({ ok: true, status: 204 }); + track('passport', 'retire'); + await flushSends(); + + expect(isTelemetryEnabled()).toBe(false); + }); +}); diff --git a/packages/internal/metrics/src/emit.ts b/packages/internal/metrics/src/emit.ts new file mode 100644 index 0000000000..bd30c0d782 --- /dev/null +++ b/packages/internal/metrics/src/emit.ts @@ -0,0 +1,60 @@ +import { isTelemetryEnabled } from './client'; +import { errorBoundary } from './errorBoundary'; +import { enqueue } from './transport'; +import type { TrackOptions } from './types'; + +const extractErrorCode = (error: Error): string | undefined => { + const maybe = error as Error & { type?: unknown; code?: unknown }; + if (typeof maybe.type === 'string' && maybe.type.length > 0) { + return maybe.type; + } + if (typeof maybe.code === 'string' && maybe.code.length > 0) { + return maybe.code; + } + if (typeof maybe.code === 'number' && Number.isFinite(maybe.code)) { + return String(maybe.code); + } + return undefined; +}; + +const toErrorPayload = (error: Error): { name?: string; code?: string } => { + const code = extractErrorCode(error); + return { + name: error.name, + ...(code ? { code } : {}), + }; +}; + +const trackFn = ( + moduleName: string, + eventName: string, + options?: TrackOptions, +): void => { + if (!isTelemetryEnabled()) { + return; + } + + const durationMs = typeof options?.durationMs === 'number' && Number.isFinite(options.durationMs) + ? Math.round(options.durationMs) + : undefined; + + enqueue({ + module: moduleName, + name: eventName, + time: Date.now(), + ...(durationMs !== undefined ? { durationMs } : {}), + ...(options?.error ? { error: toErrorPayload(options.error) } : {}), + }); +}; + +/** + * Track a function / operation invocation. + * Pass `error` when the invocation failed (name + code only; no message/stack). + * + * ```ts + * track('passport', 'login'); + * track('passport', 'login', { durationMs: 120 }); + * track('passport', 'login', { error }); + * ``` + */ +export const track = errorBoundary(trackFn); diff --git a/packages/internal/metrics/src/error.ts b/packages/internal/metrics/src/error.ts deleted file mode 100644 index 3a13c497df..0000000000 --- a/packages/internal/metrics/src/error.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { errorBoundary } from './utils/errorBoundary'; -import { track, TrackProperties } from './track'; - -type ErrorEventProperties = - | TrackProperties & { - isTrackError?: never; - errorMessage?: never; - errorStack?: never; - }; - -const trackErrorFn = ( - moduleName: string, - eventName: string, - error: Error, - properties?: ErrorEventProperties, -) => { - const { message } = error; - let stack = error.stack || ''; - const { cause } = error; - - if (cause instanceof Error) { - stack = `${stack} \nCause: ${cause.message}\n ${cause.stack}`; - } - - track(moduleName, `trackError_${eventName}`, { - ...(properties || {}), - errorMessage: message, - errorStack: stack, - isTrackError: true, - }); -}; - -/** - * Track an event and it's performance. Works similarly to `track`, but also includes a duration. - * @param moduleName Name of the module being tracked (for namespacing purposes), e.g. `passport` - * @param eventName Name of the event, e.g. `clickItem` - * @param error Error object to be tracked - * @param properties Other properties to be sent with the event, other than duration - * - * @example - * ```ts - * trackError("passport", "sendTransactionFailed", error); - * trackError("passport", "getItemFailed", error, { otherProperty: "value" }); - * ``` - */ -export const trackError = errorBoundary(trackErrorFn); diff --git a/packages/internal/metrics/src/errorBoundary.test.ts b/packages/internal/metrics/src/errorBoundary.test.ts new file mode 100644 index 0000000000..1b125294a4 --- /dev/null +++ b/packages/internal/metrics/src/errorBoundary.test.ts @@ -0,0 +1,19 @@ +import { errorBoundary } from './errorBoundary'; + +describe('errorBoundary', () => { + it('returns the result when the function does not throw', () => { + expect(errorBoundary(() => 3)()).toEqual(3); + }); + + it('returns fallback when the function throws', () => { + expect(errorBoundary((): number => { + throw new Error('test'); + }, 3)()).toEqual(3); + }); + + it('swallows async rejections', async () => { + await expect(errorBoundary(async () => { + throw new Error('test'); + }, Promise.resolve(3))()).resolves.toEqual(3); + }); +}); diff --git a/packages/internal/metrics/src/utils/errorBoundary.ts b/packages/internal/metrics/src/errorBoundary.ts similarity index 66% rename from packages/internal/metrics/src/utils/errorBoundary.ts rename to packages/internal/metrics/src/errorBoundary.ts index 538be818a5..13efde24d3 100644 --- a/packages/internal/metrics/src/utils/errorBoundary.ts +++ b/packages/internal/metrics/src/errorBoundary.ts @@ -5,18 +5,14 @@ export function errorBoundary) => ReturnType { return (...args) => { try { - // Execute the original function const result = fn(...args); if (result instanceof Promise) { - // Silent fail for now, in future - // we can send errors to a logging service return result.catch(() => fallbackResult); } return result; - } catch (error) { - // As above, fail silently for now + } catch { return fallbackResult; } }; diff --git a/packages/internal/metrics/src/flow.ts b/packages/internal/metrics/src/flow.ts deleted file mode 100644 index 83a993cfb8..0000000000 --- a/packages/internal/metrics/src/flow.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { AllowedTrackProperties, TrackProperties } from './track'; -import { errorBoundary } from './utils/errorBoundary'; -import { generateFlowId } from './utils/id'; -import { trackDuration } from './performance'; - -export type Flow = { - details: { - moduleName: string; - flowName: string; - flowId: string; - flowStartTime: number; - }; - /** - * Track an event in the flow - * @param eventName Name of the event - * @param properties Object containing event properties - */ - addEvent: (eventName: string, properties?: AllowedTrackProperties) => void; - /** - * Function to add new flow properties - * @param newProperties Object new properties - */ - addFlowProperties: (properties: AllowedTrackProperties) => void; -}; - -// Flow Tracking Functions -// ----------------------------------- -// Write a function to take multiple objects as arguments, and merge them into one object -const mergeProperties = ( - ...args: (TrackProperties | undefined)[] -): TrackProperties => { - const hasProperties = args.some((arg) => !!arg); - if (!hasProperties) { - return {}; - } - let finalProperties: Record = {}; - args.forEach((arg) => { - if (arg) { - finalProperties = { - ...finalProperties, - ...arg, - }; - } - }); - - return finalProperties; -}; - -const cleanEventName = (eventName: string) => eventName.replace(/[^a-zA-Z0-9\s\-_]/g, ''); -const getEventName = (flowName: string, eventName: string) => `${flowName}_${cleanEventName(eventName)}`; - -const trackFlowFn = ( - moduleName: string, - flowName: string, - trackStartEvent: boolean = true, - properties?: AllowedTrackProperties, -): Flow => { - // Track the start of the flow - const flowId = generateFlowId(); - const flowStartTime = Date.now(); - - // Flow tracking - let currentStepCount = 0; - let previousStepTime = 0; - - let flowProperties: TrackProperties = {}; - const mergeFlowProps = (...args: (TrackProperties | undefined)[]) => mergeProperties(flowProperties, ...args, { - flowId, - flowName, - }); - - // Set up flow properties - flowProperties = mergeFlowProps(properties); - - const addFlowProperties = (newProperties: AllowedTrackProperties) => { - if (newProperties) { - flowProperties = mergeFlowProps(newProperties); - } - }; - - const addEvent = ( - eventName: string, - eventProperties?: AllowedTrackProperties, - ) => { - const event = getEventName(flowName, eventName); - - // Calculate duration since previous step - let duration = 0; - const currentTime = performance.now(); - if (currentStepCount > 0) { - duration = currentTime - previousStepTime; - } - const mergedProps = mergeFlowProps(eventProperties, { - flowEventName: eventName, - flowStep: currentStepCount, - }); - trackDuration(moduleName, event, duration, mergedProps); - - // Increment counters - currentStepCount++; - previousStepTime = currentTime; - }; - - if (trackStartEvent) { - // Trigger a Start Event as a record of creating the flow - addEvent('Start'); - } - - return { - details: { - moduleName, - flowName, - flowId, - flowStartTime, - }, - addEvent: errorBoundary(addEvent), - addFlowProperties: errorBoundary(addFlowProperties), - }; -}; -/** - * Track a flow of events, including the start and end of the flow. - * Works similarly to `track` - * @param moduleName Name of the module being tracked (for namespacing purposes), e.g. `passport` - * @param flowName Name of the flow, e.g. `performTransaction` - * @param trackStartEvent Whether to track the start event in the flow - * @param properties Other properties to be sent with the event, other than duration - * - * @example - * ```ts - * const flow = trackFlow("passport", "performTransaction", true, { transationType: "transfer" }); - * // Do something... - * flow.addEvent("clickItem"); - * // Do something... - * flow.addFlowProperties({ item: "item1" }); - * flow.addEvent("guardianCheck", {"invisible": "true"}); - * // Do something... - * flow.addEvent("guardianCheckComplete"); - * flow.end(); - * ``` - */ -export const trackFlow = errorBoundary(trackFlowFn); diff --git a/packages/internal/metrics/src/identify.ts b/packages/internal/metrics/src/identify.ts deleted file mode 100644 index 7dcbd109ba..0000000000 --- a/packages/internal/metrics/src/identify.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { storeDetail } from './utils/state'; -import { track } from './track'; -import { Detail } from './utils/constants'; -import { errorBoundary } from './utils/errorBoundary'; - -type Identity = { - passportId?: string; - ethAddress?: string; - traits?: Record; -}; - -const parseIdentity = (params: Identity) => { - if (params.passportId) { - const key = `passport:${params.passportId.toLowerCase()}`; - return key; - } - - if (params.ethAddress) { - const key = `ethAddress:${params.ethAddress.toLowerCase()}`; - return key; - } - - throw new Error('invalid_identity'); -}; - -const identifyFn = (params: Identity) => { - const identity = parseIdentity(params); - if (!identity) { - return; - } - storeDetail(Detail.IDENTITY, identity); - track('metrics', 'identify', params.traits); -}; -export const identify = errorBoundary(identifyFn); diff --git a/packages/internal/metrics/src/index.ts b/packages/internal/metrics/src/index.ts index e5ea7db07d..7ab4e9f8d1 100644 --- a/packages/internal/metrics/src/index.ts +++ b/packages/internal/metrics/src/index.ts @@ -1,19 +1,3 @@ -// Exporting utils -import * as localStorage from './utils/localStorage'; - -export { track } from './track'; -export { trackDuration } from './performance'; -export { trackFlow } from './flow'; -export type { Flow } from './flow'; -export { trackError } from './error'; -export { identify } from './identify'; -export { - setEnvironment, - setPassportClientId, - setPublishableApiKey, - getDetail, - Detail, -} from './details'; -export const utils = { - localStorage, -}; +export { configure } from './client'; +export { track } from './emit'; +export type { MetricsConfig, TrackOptions } from './types'; diff --git a/packages/internal/metrics/src/initialise.ts b/packages/internal/metrics/src/initialise.ts deleted file mode 100644 index 2f42ff186e..0000000000 --- a/packages/internal/metrics/src/initialise.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { isNode } from './utils/browser'; -import { setClockSkew } from './utils/clock'; -import { Detail } from './utils/constants'; -import { post } from './utils/request'; -import { flattenProperties, getDetail, storeDetail } from './utils/state'; - -// WARNING: DO NOT CHANGE THE STRING BELOW. IT GETS REPLACED AT BUILD TIME. -const SDK_VERSION = '__SDK_VERSION__'; - -const getFrameParentDomain = () => { - if (isNode()) { - return ''; - } - - // If available for supported browsers (all except Firefox) - if ( - window.location.ancestorOrigins - && window.location.ancestorOrigins.length > 0 - ) { - return new URL(window.location.ancestorOrigins[0]).hostname; - } - - // Fallback to using the referrer - return document.referrer ? new URL(window.document.referrer).hostname : ''; -}; - -const runtimeHost = () => { - if (isNode()) { - return ''; - } - - let domain; - try { - if (window.self !== window.top) { - domain = getFrameParentDomain(); - } - } catch (error) { - // Do nothing - } - - // Fallback to current domain if can't detect parent domain - if (!domain) { - domain = window.location.hostname; - } - return domain; -}; - -type RuntimeDetails = { - sdkVersion: string; - browser: string; - domain?: string; - tz?: string; - screen?: string; -}; - -const getRuntimeDetails = (): RuntimeDetails => { - storeDetail(Detail.SDK_VERSION, SDK_VERSION); - - if (isNode()) { - return { browser: 'nodejs', sdkVersion: SDK_VERSION }; - } - - const domain = runtimeHost(); - if (domain) { - storeDetail(Detail.DOMAIN, domain); - } - - return { - sdkVersion: SDK_VERSION, - browser: window.navigator.userAgent, - domain, - tz: Intl.DateTimeFormat().resolvedOptions().timeZone, - screen: `${window.screen.width}x${window.screen.height}`, - }; -}; - -type InitialiseResponse = { - runtimeId: string; - sTime: string; -}; - -let initialised = false; -export const isInitialised = () => initialised; - -export const initialise = async () => { - initialised = true; - try { - const runtimeDetails = flattenProperties(getRuntimeDetails()); - const existingRuntimeId = getDetail(Detail.RUNTIME_ID); - const existingIdentity = getDetail(Detail.IDENTITY); - - const body = { - version: 1, - data: { - runtimeDetails, - runtimeId: existingRuntimeId, - uId: existingIdentity, - }, - }; - const response = await post('/v1/sdk/initialise', body); - - // Get runtimeId and store it - const { runtimeId, sTime } = response; - storeDetail(Detail.RUNTIME_ID, runtimeId); - setClockSkew(sTime); - } catch (error) { - initialised = false; - } -}; diff --git a/packages/internal/metrics/src/performance.ts b/packages/internal/metrics/src/performance.ts deleted file mode 100644 index cd7ec93200..0000000000 --- a/packages/internal/metrics/src/performance.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { track, AllowedTrackProperties } from './track'; - -/** - * Track an event and it's performance. Works similarly to `track`, but also includes a duration. - * @param moduleName Name of the module being tracked (for namespacing purposes), e.g. `passport` - * @param eventName Name of the event, e.g. `clickItem` - * @param duration Duration of the event in milliseconds, e.g. `1000` - * @param properties Other properties to be sent with the event, other than duration - * - * @example - * ```ts - * trackDuration("passport", "performTransaction", 1000); - * trackDuration("passport", "performTransaction", 1000, { transationType: "transfer" }); - * ``` - */ -export const trackDuration = ( - moduleName: string, - eventName: string, - duration: number, - properties?: AllowedTrackProperties, -) => track(moduleName, eventName, { - ...(properties || {}), - duration: Math.round(duration), -}); diff --git a/packages/internal/metrics/src/runtimeId.test.ts b/packages/internal/metrics/src/runtimeId.test.ts new file mode 100644 index 0000000000..6e590ba262 --- /dev/null +++ b/packages/internal/metrics/src/runtimeId.test.ts @@ -0,0 +1,21 @@ +import { generateRuntimeId } from './runtimeId'; + +/** Mirrors sdk-analytics `service.ValidateHash`. */ +const validateHash = (hash: string): boolean => { + if (hash.length < 2) { + return false; + } + const firstCharDec = Number.parseInt(hash[1]!, 16); + if (Number.isNaN(firstCharDec) || hash.length <= firstCharDec) { + return false; + } + return hash[hash.length - 1] === hash[firstCharDec]; +}; + +describe('generateRuntimeId', () => { + it('produces ids that pass the backend ValidateHash checksum', () => { + for (let i = 0; i < 20; i += 1) { + expect(validateHash(generateRuntimeId())).toBe(true); + } + }); +}); diff --git a/packages/internal/metrics/src/runtimeId.ts b/packages/internal/metrics/src/runtimeId.ts new file mode 100644 index 0000000000..4cf15a0fc3 --- /dev/null +++ b/packages/internal/metrics/src/runtimeId.ts @@ -0,0 +1,18 @@ +/** + * Opaque session id that satisfies sdk-analytics' ValidateHash checksum. + * Random per page session — not a device fingerprint and not persisted. + */ +export const generateRuntimeId = (): string => { + const bytes = new Uint8Array(32); + if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { + crypto.getRandomValues(bytes); + } else { + for (let i = 0; i < bytes.length; i += 1) { + bytes[i] = Math.floor(Math.random() * 256); + } + } + + const hash = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); + const index = Number.parseInt(hash[1]!, 16); + return `${hash}${hash[index]!}`; +}; diff --git a/packages/internal/metrics/src/track.ts b/packages/internal/metrics/src/track.ts deleted file mode 100644 index f4b022201d..0000000000 --- a/packages/internal/metrics/src/track.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { initialise, isInitialised } from './initialise'; -import { - addEvent, - flattenProperties, - getAllDetails, - getEvents, - removeSentEvents, -} from './utils/state'; -import { errorBoundary } from './utils/errorBoundary'; -import { post } from './utils/request'; -import { isTestEnvironment } from './utils/checkEnv'; -import { - getGlobalisedCachedFunction, - getGlobalisedValue, -} from './utils/globalise'; -import { getCorrectedTime } from './utils/clock'; - -export const POLLING_FREQUENCY = 5000; - -export type TrackProperties = Record< -string, -string | number | boolean | undefined ->; - -// List of properties that are allowed to be sent with the track request -// As these are used by other types of tracking -export type AllowedTrackProperties = TrackProperties & { - // Performance - duration?: never; - // Flow - flowId?: never; - flowName?: never; - flowEventName?: never; - flowStep?: never; - // Error - isTrackError?: never; - errorMessage?: never; - errorStack?: never; -}; - -const trackFn = ( - moduleName: string, - eventName: string, - properties?: TrackProperties, -): void => { - const event = { - event: `${moduleName}.${eventName}`, - time: getCorrectedTime(), - ...(properties && { properties: flattenProperties(properties) }), - }; - addEvent(event); -}; - -/** - * Track an event completion. - * @param moduleName Name of the module being tracked (for namespacing purposes), e.g. `passport` - * @param eventName Name of the event, use camelCase e.g. `clickItem` - * @param properties Other properties to be sent with the event - * - * e.g. - * - * ```ts - * track("passport", "performTransaction"); - * track("passport", "performTransaction", { transationType: "transfer" }); - * ``` - */ -export const track = errorBoundary( - getGlobalisedCachedFunction('track', trackFn), -); - -// Sending events to the server -const flushFn = async () => { - // Don't flush if not initialised - if (isInitialised() === false) { - await initialise(); - return; - } - - const events = getEvents(); - if (events.length === 0) { - return; - } - // Track events length here, incase - const numEvents = events.length; - - // Get details and send it with the track request - const details = getAllDetails(); - - const metricsPayload = { - version: 1, - data: { - events, - details, - }, - }; - - const response = await post('/v1/sdk/metrics', metricsPayload); - if (response instanceof Error) { - return; - } - - // Clear events if successfully posted - removeSentEvents(numEvents); -}; -const flush = errorBoundary(flushFn); - -// Flush events every 5 seconds -const flushPoll = async () => { - await flush(); - setTimeout(flushPoll, POLLING_FREQUENCY); -}; - -let flushingStarted = false; -const startFlushing = () => { - if (flushingStarted) { - return; - } - flushingStarted = true; - flushPoll(); -}; - -// This will get initialised when module is imported. -if (!isTestEnvironment()) { - errorBoundary(getGlobalisedValue('startFlushing', startFlushing))(); -} diff --git a/packages/internal/metrics/src/transport.ts b/packages/internal/metrics/src/transport.ts new file mode 100644 index 0000000000..1173c6e3b9 --- /dev/null +++ b/packages/internal/metrics/src/transport.ts @@ -0,0 +1,84 @@ +import { + disableTelemetry, + getClientId, + getRuntimeId, + getSdkVersion, + isTelemetryEnabled, +} from './client'; +import type { MetricEvent } from './types'; +import { toV1WireEnvelope } from './wire'; + +const IMTBL_API = 'https://api.immutable.com'; + +const encodeBase64 = (payload: string): string => { + if (typeof Buffer !== 'undefined') { + return Buffer.from(payload, 'utf-8').toString('base64'); + } + if (typeof btoa === 'function') { + return btoa(unescape(encodeURIComponent(payload))); + } + throw new Error('Base64 encoding not supported in this environment'); +}; + +const postMetrics = async (events: MetricEvent[]): Promise => { + const envelope = toV1WireEnvelope(events, { + rid: getRuntimeId(), + sdkVersion: getSdkVersion(), + clientId: getClientId(), + }); + + const response = await fetch(`${IMTBL_API}/v1/sdk/metrics`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ payload: encodeBase64(JSON.stringify(envelope)) }), + }); + return response.status; +}; + +let pending: MetricEvent[] = []; +let flushScheduled = false; + +const send = async (events: MetricEvent[]): Promise => { + if (!isTelemetryEnabled() || events.length === 0) { + return; + } + + try { + const status = await postMetrics(events); + if (status === 204 || status < 200 || status >= 300) { + disableTelemetry(); + } + } catch { + disableTelemetry(); + } +}; + +/** Fire-and-forget send; coalesces events in the same JS tick. */ +export const enqueue = (event: MetricEvent): void => { + if (!isTelemetryEnabled()) { + return; + } + + pending.push(event); + if (flushScheduled) { + return; + } + + flushScheduled = true; + queueMicrotask(() => { + flushScheduled = false; + if (!isTelemetryEnabled()) { + pending = []; + return; + } + const batch = pending; + pending = []; + send(batch).catch(() => undefined); + }); +}; + +/** Reset for unit tests only. */ +export const resetTransportForTests = (): void => { + pending = []; + flushScheduled = false; +}; diff --git a/packages/internal/metrics/src/types.ts b/packages/internal/metrics/src/types.ts new file mode 100644 index 0000000000..12f7c277dd --- /dev/null +++ b/packages/internal/metrics/src/types.ts @@ -0,0 +1,16 @@ +export type TrackOptions = { + durationMs?: number; + error?: Error; +}; + +export type MetricEvent = { + module: string; + name: string; + time: number; + durationMs?: number; + error?: { name?: string; code?: string }; +}; + +export type MetricsConfig = { + clientId?: string; +}; diff --git a/packages/internal/metrics/src/utils/browser.ts b/packages/internal/metrics/src/utils/browser.ts deleted file mode 100644 index 888c20c7b1..0000000000 --- a/packages/internal/metrics/src/utils/browser.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const isNode = () => typeof window === 'undefined'; - -export const isBrowser = () => !isNode(); diff --git a/packages/internal/metrics/src/utils/checkEnv.ts b/packages/internal/metrics/src/utils/checkEnv.ts deleted file mode 100644 index 25ed31755a..0000000000 --- a/packages/internal/metrics/src/utils/checkEnv.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { isBrowser } from './browser'; -import { errorBoundary } from './errorBoundary'; - -function isTestEnvironmentFn() { - if (isBrowser()) { - return false; - } - - if (typeof process === 'undefined') { - return false; - } - - // Consider using `ci-info` package for better results, though might fail as not browser safe. - // Just use process.env.CI for now. - return process.env.JEST_WORKER_ID !== undefined; -} - -export const isTestEnvironment = errorBoundary(isTestEnvironmentFn, false); diff --git a/packages/internal/metrics/src/utils/clock.ts b/packages/internal/metrics/src/utils/clock.ts deleted file mode 100644 index dfa950d350..0000000000 --- a/packages/internal/metrics/src/utils/clock.ts +++ /dev/null @@ -1,17 +0,0 @@ -// These functions are to handle clock skew between the browser and the server. -let clockSkew = 0; - -export const setClockSkew = (serverUnixTime: string) => { - // Get the server time in milliseconds - const sTime = parseInt(serverUnixTime, 10) * 1000; - const serverTime = new Date(sTime); - const now = new Date(); - clockSkew = serverTime.getTime() - now.getTime(); - return clockSkew; -}; - -export const getCorrectedTime = () => { - const now = new Date().getTime() + clockSkew; - const fixedDate = new Date(now).toISOString(); - return fixedDate; -}; diff --git a/packages/internal/metrics/src/utils/constants.ts b/packages/internal/metrics/src/utils/constants.ts deleted file mode 100644 index f77a3a8d16..0000000000 --- a/packages/internal/metrics/src/utils/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum Detail { - RUNTIME_ID = 'rid', - PASSPORT_CLIENT_ID = 'passportClientId', - ENVIRONMENT = 'env', - PUBLISHABLE_API_KEY = 'pak', - IDENTITY = 'uid', - DOMAIN = 'domain', - SDK_VERSION = 'sdkVersion', -} diff --git a/packages/internal/metrics/src/utils/errorBoundary.test.ts b/packages/internal/metrics/src/utils/errorBoundary.test.ts deleted file mode 100644 index b1000d45f5..0000000000 --- a/packages/internal/metrics/src/utils/errorBoundary.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { errorBoundary } from './errorBoundary'; - -describe('errorBoundary', () => { - it('should return the result of the function if it does not throw', () => { - const testFn = () => 3; - expect(errorBoundary(testFn)()).toEqual(3); - }); - it('should pass through arguments to the wrapped function accurately', () => { - const testFn = jest.fn(); - errorBoundary(testFn)(1, 2, 3); - expect(testFn).toHaveBeenCalledWith(1, 2, 3); - }); - it('should return the result of a promise function if it does not throw', () => { - const testFn = async () => 3; - expect(errorBoundary(testFn)()).resolves.toEqual(3); - }); - it('should not throw an error for a function that throws', () => { - const testFn = () => { - throw new Error('test'); - }; - expect(errorBoundary(testFn)).not.toThrowError(); - }); - it('should not throw an error for an async funtion that errors', () => { - const testFn = async () => { - throw new Error('test'); - }; - expect(errorBoundary(testFn)).not.toThrowError(); - }); - it('should return the fallback result for a function that throws', () => { - const testFn = (): number => { - throw new Error('test'); - }; - expect(errorBoundary(testFn, 3)()).toEqual(3); - }); - it('should return the fallback result for an async function that throws', () => { - const testFn = async (): Promise => { - throw new Error('test'); - }; - expect(errorBoundary(testFn, Promise.resolve(3))()).resolves.toEqual(3); - }); -}); diff --git a/packages/internal/metrics/src/utils/globalise.ts b/packages/internal/metrics/src/utils/globalise.ts deleted file mode 100644 index 0e82996d41..0000000000 --- a/packages/internal/metrics/src/utils/globalise.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { memorise } from 'lru-memorise'; -import { getGlobalisedValue as globalise } from 'global-const'; - -const GLOBALISE_KEY = 'imtbl__metrics'; -const MEMORISE_TIMEFRAME = 5000; -const MEMORISE_MAX = 1000; - -export const getGlobalisedValue = (key: string, value: T): T => globalise(GLOBALISE_KEY, key, value); - -export const getGlobalisedCachedFunction = any>( - key: string, - fn: T, -): T => { - // Some applications (esp backend, or frontends using the split bundles) can sometimes - // initialise the same request multiple times. This will prevent multiple of the - // same event,value from being reported in a 1 second period. - const memorisedFn = memorise(fn, { - lruOptions: { ttl: MEMORISE_TIMEFRAME, max: MEMORISE_MAX }, - }) as unknown as T; - - return globalise(GLOBALISE_KEY, key, memorisedFn); -}; diff --git a/packages/internal/metrics/src/utils/id.ts b/packages/internal/metrics/src/utils/id.ts deleted file mode 100644 index 8a7c7c0164..0000000000 --- a/packages/internal/metrics/src/utils/id.ts +++ /dev/null @@ -1,7 +0,0 @@ -// UUID not playing well with browser, using this for now -export const generateFlowId = () => { - const s4 = () => Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; -}; diff --git a/packages/internal/metrics/src/utils/localStorage.test.ts b/packages/internal/metrics/src/utils/localStorage.test.ts deleted file mode 100644 index 6ab7bfbe2c..0000000000 --- a/packages/internal/metrics/src/utils/localStorage.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { deleteItem, getItem, setItem } from './localStorage'; - -beforeEach(() => { - global.localStorage.clear(); -}); - -describe('getItem', () => { - test('it should not return a value that does not exist', () => { - const value = getItem('test'); - expect(value).toBe(undefined); - }); - test('it should return a string value when stored', () => { - global.localStorage.setItem('__IMX-test', 'some value'); - expect(getItem('test')).toBe('some value'); - }); -}); - -describe('setItem', () => { - test('it should store items in a namespaced key', () => { - setItem('test', 1); - expect(global.localStorage.getItem('__IMX-test')).toBe('1'); - }); - - test('it should serialise an object accurately when storing', () => { - const returnVal = setItem('test', { a: 1, b: 'hello' }); - expect(global.localStorage.getItem('__IMX-test')).toBe( - JSON.stringify({ a: 1, b: 'hello' }), - ); - expect(returnVal).toBe(true); - }); - - test('it should handle null values accurately', () => { - setItem('isNull', null); - expect(getItem('isNull')).toBe(null); - expect(getItem('doesnotexist')).toBe(undefined); - }); -}); - -describe('deleteItem', () => { - test('should remove item that is stored', () => { - global.localStorage.setItem('__IMX-test', 'test'); - deleteItem('test'); - expect(global.localStorage.getItem('__IMX--test')).toBeNull(); - }); - test('should do nothing if key not stored', () => { - expect(() => global.localStorage.getItem('__IMX-random')).not.toThrow(); - }); -}); diff --git a/packages/internal/metrics/src/utils/localStorage.ts b/packages/internal/metrics/src/utils/localStorage.ts deleted file mode 100644 index b9028bed99..0000000000 --- a/packages/internal/metrics/src/utils/localStorage.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Abstraction on localstorage - */ - -import { isBrowser } from './browser'; - -const localStoragePrefix = '__IMX-'; - -const hasLocalstorage = () => isBrowser() && window.localStorage; - -const parseItem = (payload: string | null) => { - // Try to parse, if can't be parsed assume string - // and return string - if (payload === null) return undefined; - - try { - return JSON.parse(payload); - } catch (error) { - // Assumes it's a string. - return payload; - } -}; - -const serialiseItem = (payload: any) => { - if (typeof payload === 'string') { - return payload; - } - return JSON.stringify(payload); -}; - -/** - * GenKey will take into account the namespace - * as well as if being run in the Link, it will tap into the link - * @param {string} key - * @returns key - */ -const genKey = (key: string) => `${localStoragePrefix}${key}`; - -export function getItem(key: string): T | undefined { - if (hasLocalstorage()) { - return parseItem(window.localStorage.getItem(genKey(key))) as T; - } - return undefined; -} - -export const setItem = (key: string, payload: any): boolean => { - if (hasLocalstorage()) { - window.localStorage.setItem(genKey(key), serialiseItem(payload)); - return true; - } - return false; -}; - -export const deleteItem = (key: string): boolean => { - if (hasLocalstorage()) { - window.localStorage.removeItem(genKey(key)); - return true; - } - return false; -}; diff --git a/packages/internal/metrics/src/utils/request.ts b/packages/internal/metrics/src/utils/request.ts deleted file mode 100644 index 9e7a85738e..0000000000 --- a/packages/internal/metrics/src/utils/request.ts +++ /dev/null @@ -1,33 +0,0 @@ -const IMTBL_API = 'https://api.immutable.com'; - -const encodeBase64 = (payload: string): string => { - if (typeof Buffer !== 'undefined') { - return Buffer.from(payload, 'utf-8').toString('base64'); - } - if (typeof btoa === 'function') { - return btoa(unescape(encodeURIComponent(payload))); - } - throw new Error('Base64 encoding not supported in this environment'); -}; - -export async function post(path: string, data: unknown): Promise { - const payload = JSON.stringify(data); - const body = { - payload: encodeBase64(payload), - }; - - const response = await fetch(`${IMTBL_API}${path}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - const text = await response.text().catch(() => ''); - throw new Error(`Request failed (${response.status}): ${text || response.statusText}`); - } - - return response.json() as Promise; -} diff --git a/packages/internal/metrics/src/utils/state.ts b/packages/internal/metrics/src/utils/state.ts deleted file mode 100644 index 3aa0e26ffc..0000000000 --- a/packages/internal/metrics/src/utils/state.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { getItem, setItem } from './localStorage'; -import { Detail } from './constants'; - -export enum Store { - EVENTS = 'metrics-events', - RUNTIME = 'metrics-runtime', -} - -// In memory storage for events and other data -let EVENT_STORE: any[]; -let RUNTIME_DETAILS: Record; - -// Initialise store and runtime -const initialise = () => { - EVENT_STORE = getItem(Store.EVENTS) || []; - RUNTIME_DETAILS = getItem(Store.RUNTIME) || {}; -}; -initialise(); - -// Runtime Details -export const storeDetail = (key: Detail, value: string) => { - RUNTIME_DETAILS = { - ...RUNTIME_DETAILS, - [key]: value, - }; - setItem(Store.RUNTIME, RUNTIME_DETAILS); -}; -export const getDetail = (key: Detail) => { - // Handle the scenario where detail is a falsy value - if (RUNTIME_DETAILS[key] === undefined) { - return undefined; - } - return RUNTIME_DETAILS[key]; -}; - -export const getAllDetails = () => RUNTIME_DETAILS; - -// Events -export const getEvents = () => EVENT_STORE; - -export const addEvent = (event: any) => { - EVENT_STORE.push(event); - setItem(Store.EVENTS, EVENT_STORE); -}; - -export const removeSentEvents = (numberOfEvents: number) => { - EVENT_STORE = EVENT_STORE.slice(numberOfEvents); - setItem(Store.EVENTS, EVENT_STORE); -}; - -type TrackProperties = Record< -string, -string | number | boolean | undefined ->; - -export const flattenProperties = (properties: TrackProperties) => { - const propertyMap: [string, string][] = []; - Object.entries(properties).forEach(([key, value]) => { - if ( - typeof key === 'string' - || typeof value === 'string' - || typeof value === 'number' - || typeof value === 'boolean' - ) { - propertyMap.push([key, value!.toString()]); - } - }); - return propertyMap; -}; diff --git a/packages/internal/metrics/src/wire.ts b/packages/internal/metrics/src/wire.ts new file mode 100644 index 0000000000..fe60cf1210 --- /dev/null +++ b/packages/internal/metrics/src/wire.ts @@ -0,0 +1,72 @@ +import type { MetricEvent } from './types'; + +/** Legacy sdk-analytics `/v1/sdk/metrics` envelope (version ≤ 1). */ +export type V1WireEnvelope = { + version: 1; + data: { + details: { + rid: string; + sdkVersion: string; + passportClientId?: string; + }; + events: V1WireEvent[]; + }; +}; + +type V1WireEvent = { + event: string; + time: string; + properties?: [string, string][]; +}; + +const toV1WireEvent = (event: MetricEvent): V1WireEvent => { + const { + module, + name: eventName, + time, + durationMs, + error, + } = event; + const properties: [string, string][] = []; + let name = eventName; + + if (typeof durationMs === 'number') { + properties.push(['durationMs', String(durationMs)]); + } + + if (error) { + name = `trackError_${eventName}`; + properties.push(['isTrackError', 'true']); + if (error.name) { + properties.push(['errorName', error.name]); + } + if (error.code) { + properties.push(['errorCode', error.code]); + } + } + + return { + event: `${module}.${name}`, + time: new Date(time).toISOString(), + ...(properties.length > 0 ? { properties } : {}), + }; +}; + +/** + * Map slim internal events to the legacy v1 wire shape the backend expects. + * Errors become `module.trackError_name` + `isTrackError` so NR path stays intact. + */ +export const toV1WireEnvelope = ( + events: MetricEvent[], + details: { rid: string; sdkVersion: string; clientId?: string }, +): V1WireEnvelope => ({ + version: 1, + data: { + details: { + rid: details.rid, + sdkVersion: details.sdkVersion, + ...(details.clientId ? { passportClientId: details.clientId } : {}), + }, + events: events.map(toV1WireEvent), + }, +}); diff --git a/packages/minting-backend/sdk/package.json b/packages/minting-backend/sdk/package.json index 63bb16a28a..ab3c605e2d 100644 --- a/packages/minting-backend/sdk/package.json +++ b/packages/minting-backend/sdk/package.json @@ -8,7 +8,6 @@ "@imtbl/blockchain-data": "workspace:*", "@imtbl/config": "workspace:*", "@imtbl/generated-clients": "workspace:*", - "@imtbl/metrics": "workspace:*", "@imtbl/webhook": "workspace:*", "uuid": "^9.0.1" }, diff --git a/packages/minting-backend/sdk/src/analytics/index.ts b/packages/minting-backend/sdk/src/analytics/index.ts deleted file mode 100644 index 7742d79a0d..0000000000 --- a/packages/minting-backend/sdk/src/analytics/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { track } from '@imtbl/metrics'; - -const moduleName = 'minting_backend_sdk'; - -export const trackInitializePersistencePG = () => { - try { - track(moduleName, 'initializePersistencePG'); - } catch { - // ignore - } -}; - -export const trackInitializePersistencePrismaSqlite = () => { - try { - track(moduleName, 'initializePersistencePrismaSqlite'); - } catch { - // ignore - } -}; - -export const trackSubmitMintingRequests = () => { - try { - track(moduleName, 'submitMintingRequests'); - } catch { - // ignore - } -}; - -export const trackProcessMint = () => { - try { - track(moduleName, 'processMint'); - } catch { - // ignore - } -}; - -export const trackRecordMint = () => { - try { - track(moduleName, 'recordMint'); - } catch { - // ignore - } -}; - -export const trackError = (error: Error) => { - try { - track(moduleName, 'error', { - name: error.name, - message: error.message - }); - } catch { - // ignore - } -}; - -export const trackUncaughtException = (error: Error, origin: string) => { - try { - track(moduleName, 'error', { - name: error.name, - message: error.message, - origin, - }); - } catch { - // ignore - } -}; diff --git a/packages/minting-backend/sdk/src/index.ts b/packages/minting-backend/sdk/src/index.ts index ccefca65f1..cc05b8b674 100644 --- a/packages/minting-backend/sdk/src/index.ts +++ b/packages/minting-backend/sdk/src/index.ts @@ -1,8 +1,6 @@ import { ImmutableConfiguration, ModuleConfiguration } from '@imtbl/config'; import { BlockchainData } from '@imtbl/blockchain-data'; import { ZkevmMintRequestUpdated, handle } from '@imtbl/webhook'; -import { setEnvironment, setPublishableApiKey } from '@imtbl/metrics'; -import { trackUncaughtException } from './analytics'; import { mintingPersistence as mintingPersistencePg } from './persistence/pg/postgres'; import { mintingPersistence as mintingPersistencePrismaSqlite } from './persistence/prismaSqlite/sqlite'; import { @@ -44,11 +42,6 @@ export class MintingBackendModule { this.blockchainDataClient = new BlockchainData({ baseConfig: config.baseConfig }); - - setEnvironment(this.baseConfig.environment); - if (this.baseConfig.publishableKey) { - setPublishableApiKey(this.baseConfig.publishableKey); - } } /** @@ -98,11 +91,3 @@ export class MintingBackendModule { }); } } - -if (typeof process !== 'undefined' && process.on) { - try { - process.on('uncaughtExceptionMonitor', trackUncaughtException); - } catch { - // ignore - } -} diff --git a/packages/minting-backend/sdk/src/minting.ts b/packages/minting-backend/sdk/src/minting.ts index f663453b08..136ba50a7b 100644 --- a/packages/minting-backend/sdk/src/minting.ts +++ b/packages/minting-backend/sdk/src/minting.ts @@ -6,19 +6,11 @@ import { BlockchainData } from '@imtbl/blockchain-data'; import { ZkevmMintRequestUpdated } from '@imtbl/webhook'; import { CreateMintRequest, MintRequest, MintingPersistence } from './persistence/type'; import { Logger } from './logger/type'; -import { - trackError, trackProcessMint, trackRecordMint, trackSubmitMintingRequests -} from './analytics'; - -// TODO: expose metrics -// - submitting status count, conflicting status count -// - failed events count export const recordMint = async ( mintingPersistence: MintingPersistence, mintRequest: CreateMintRequest ) => { - trackRecordMint(); mintingPersistence.recordMint(mintRequest); }; @@ -38,7 +30,6 @@ export const submitMintingRequests = async ( logger: Logger = console, maxLoops = Infinity, ) => { - trackSubmitMintingRequests(); let mintingResponse: Types.CreateMintRequestResult | undefined; let numberOfLoops = 0; while (numberOfLoops++ < maxLoops) { @@ -128,8 +119,6 @@ export const submitMintingRequests = async ( return response; } catch (e: any) { logger.error(e); - trackError(e); - if ( e.code === 'CONFLICT_ERROR' && e.details?.id === 'reference_id' @@ -149,7 +138,6 @@ export const submitMintingRequests = async ( ); } catch (e2) { logger.error(e2); - trackError(e); } } else { // separate assets into "need to be retied" and "exceeded max number of tries." @@ -199,7 +187,6 @@ export const processMint = async ( event: ZkevmMintRequestUpdated, logger: Logger = console ) => { - trackProcessMint(); if (event.event_name !== 'imtbl_zkevm_mint_request_updated') { logger.info( `${event.event_name} is not imtbl_zkevm_mint_request_updated, skip.` diff --git a/packages/minting-backend/sdk/src/persistence/pg/postgres.ts b/packages/minting-backend/sdk/src/persistence/pg/postgres.ts index 69ced26a7e..51be0f1cac 100644 --- a/packages/minting-backend/sdk/src/persistence/pg/postgres.ts +++ b/packages/minting-backend/sdk/src/persistence/pg/postgres.ts @@ -1,27 +1,24 @@ import type { Pool } from 'pg'; import { CreateMintRequest, MintingPersistence, SubmittedMintRequest } from '../type'; -import { trackInitializePersistencePG } from '../../analytics'; -export const mintingPersistence = (client: Pool): MintingPersistence => { - trackInitializePersistencePG(); - return { - recordMint: async (request: CreateMintRequest) => { - const r = await client.query( - ` +export const mintingPersistence = (client: Pool): MintingPersistence => ({ + recordMint: async (request: CreateMintRequest) => { + const r = await client.query( + ` INSERT INTO im_assets (asset_id, contract_address, owner_address, metadata, amount, token_id) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (asset_id, contract_address) DO NOTHING; `, - [ - request.asset_id, request.contract_address, request.owner_address, - request.metadata, request.amount, request.token_id - ] - ); - if (r.rowCount === 0) { - throw new Error('Duplicated mint'); - } - }, - getNextBatchForSubmission: async (limit: number) => { - const res = await client.query(` + [ + request.asset_id, request.contract_address, request.owner_address, + request.metadata, request.amount, request.token_id + ] + ); + if (r.rowCount === 0) { + throw new Error('Duplicated mint'); + } + }, + getNextBatchForSubmission: async (limit: number) => { + const res = await client.query(` WITH limited_assets AS ( SELECT id FROM im_assets @@ -35,16 +32,16 @@ export const mintingPersistence = (client: Pool): MintingPersistence => { AND id IN (SELECT id FROM limited_assets) RETURNING *; `, [limit]); - return res.rows; - }, - updateMintingStatusToSubmitted: async (ids: string[]) => { - await client.query(` + return res.rows; + }, + updateMintingStatusToSubmitted: async (ids: string[]) => { + await client.query(` UPDATE im_assets SET minting_status = $2 WHERE id = ANY($1); `, [ids, 'submitted']); - }, - syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => { - // doing a upsert just in case the row has not been created yet - await client.query(` + }, + syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => { + // doing a upsert just in case the row has not been created yet + await client.query(` INSERT INTO im_assets ( asset_id, contract_address, @@ -68,46 +65,45 @@ export const mintingPersistence = (client: Pool): MintingPersistence => { im_assets.last_imtbl_zkevm_mint_request_updated_id is null ); `, [ - submittedMintRequest.assetId, - submittedMintRequest.contractAddress, - submittedMintRequest.ownerAddress, - submittedMintRequest.tokenId, - submittedMintRequest.status, - submittedMintRequest.metadataId, - submittedMintRequest.imtblZkevmMintRequestUpdatedId, - submittedMintRequest.error, - submittedMintRequest.amount - ]); - }, - markAsConflict: async (assetIds: string[], contractAddress: string) => { - await client.query(` + submittedMintRequest.assetId, + submittedMintRequest.contractAddress, + submittedMintRequest.ownerAddress, + submittedMintRequest.tokenId, + submittedMintRequest.status, + submittedMintRequest.metadataId, + submittedMintRequest.imtblZkevmMintRequestUpdatedId, + submittedMintRequest.error, + submittedMintRequest.amount + ]); + }, + markAsConflict: async (assetIds: string[], contractAddress: string) => { + await client.query(` UPDATE im_assets SET minting_status = 'conflicting' WHERE asset_id = ANY($1) AND contract_address = $2; `, [assetIds, contractAddress]); - }, - resetMintingStatus: async (ids: string[]) => { - await client.query(` + }, + resetMintingStatus: async (ids: string[]) => { + await client.query(` UPDATE im_assets SET minting_status = null WHERE id = ANY($1); `, [ids]); - }, - markForRetry: async (ids: string[]) => { - await client.query(` + }, + markForRetry: async (ids: string[]) => { + await client.query(` UPDATE im_assets SET minting_status = null, tried_count = tried_count + 1 WHERE id = ANY($1); `, [ids]); - }, - updateMintingStatusToSubmissionFailed: async (ids: string[]) => { - await client.query(` + }, + updateMintingStatusToSubmissionFailed: async (ids: string[]) => { + await client.query(` UPDATE im_assets SET minting_status = 'submission_failed' WHERE id = ANY($1); `, [ids]); - }, - getMintingRequest: async (contractAddress: string, referenceId: string) => { - const res = await client.query(` + }, + getMintingRequest: async (contractAddress: string, referenceId: string) => { + const res = await client.query(` SELECT * FROM im_assets WHERE contract_address = $1 and asset_id = $2; `, [contractAddress, referenceId]); - return res.rows[0] || null; - } - }; -}; + return res.rows[0] || null; + } +}); diff --git a/packages/minting-backend/sdk/src/persistence/prismaSqlite/sqlite.ts b/packages/minting-backend/sdk/src/persistence/prismaSqlite/sqlite.ts index 4c4953e0aa..02b8c2f129 100644 --- a/packages/minting-backend/sdk/src/persistence/prismaSqlite/sqlite.ts +++ b/packages/minting-backend/sdk/src/persistence/prismaSqlite/sqlite.ts @@ -1,230 +1,226 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable @typescript-eslint/naming-convention */ -import { trackInitializePersistencePrismaSqlite } from '../../analytics'; import { CreateMintRequest, MintingPersistence, SubmittedMintRequest } from '../type'; // client is a PrismaClient instance. it needs to be generated with // additional schema defined inside (TODO: point to repo) file. -export const mintingPersistence = (client: any): MintingPersistence => { - trackInitializePersistencePrismaSqlite(); - return { - recordMint: async (request: CreateMintRequest) => { - const result = await client.imAssets.upsert({ - where: { - im_assets_uindex: { - assetId: request.asset_id, contractAddress: request.contract_address - } - }, - update: {}, // Do nothing on conflict - create: { - assetId: request.asset_id, - contractAddress: request.contract_address, - ownerAddress: request.owner_address, - metadata: JSON.stringify(request.metadata), // Serialize JSON metadata - amount: request.amount || null, - tokenId: request.token_id || null, - }, - }); +export const mintingPersistence = (client: any): MintingPersistence => ({ + recordMint: async (request: CreateMintRequest) => { + const result = await client.imAssets.upsert({ + where: { + im_assets_uindex: { + assetId: request.asset_id, contractAddress: request.contract_address + } + }, + update: {}, // Do nothing on conflict + create: { + assetId: request.asset_id, + contractAddress: request.contract_address, + ownerAddress: request.owner_address, + metadata: JSON.stringify(request.metadata), // Serialize JSON metadata + amount: request.amount || null, + tokenId: request.token_id || null, + }, + }); - // Since upsert does not throw on update and does not tell directly if it updated or inserted, - // we need to add additional logic to handle this if "result" does not provide enough info. - if (!result) { - throw new Error('Duplicated mint'); - } - }, - // WARNING: this is NOT concurrency safe. Please only call this method one at a time. - getNextBatchForSubmission: async (limit: number) => { - const assets = await client.imAssets.findMany({ - where: { - mintingStatus: null - }, - take: limit - }); + // Since upsert does not throw on update and does not tell directly if it updated or inserted, + // we need to add additional logic to handle this if "result" does not provide enough info. + if (!result) { + throw new Error('Duplicated mint'); + } + }, + // WARNING: this is NOT concurrency safe. Please only call this method one at a time. + getNextBatchForSubmission: async (limit: number) => { + const assets = await client.imAssets.findMany({ + where: { + mintingStatus: null + }, + take: limit + }); - const assetIds = assets.map((asset: { id: string; }) => asset.id); + const assetIds = assets.map((asset: { id: string; }) => asset.id); - await client.imAssets.updateMany({ - where: { - id: { - in: assetIds - } - }, - data: { - mintingStatus: 'submitting' + await client.imAssets.updateMany({ + where: { + id: { + in: assetIds } - }); + }, + data: { + mintingStatus: 'submitting' + } + }); - const updatedAssets = await client.imAssets.findMany({ - where: { - id: { - in: assetIds - } + const updatedAssets = await client.imAssets.findMany({ + where: { + id: { + in: assetIds } - }); - return updatedAssets.map((asset: any) => ({ - id: asset.id, - contract_address: asset.contractAddress, - wallet_address: asset.ownerAddress, - asset_id: asset.assetId, - metadata: asset.metadata ? JSON.parse(asset.metadata) : null, - owner_address: asset.ownerAddress, - tried_count: asset.triedCount, - amount: asset.amount || null, - token_id: asset.tokenId || null, - })); - }, - updateMintingStatusToSubmitted: async (ids: string[]) => { - await client.imAssets.updateMany({ - where: { - id: { - in: ids - } - }, - data: { - mintingStatus: 'submitted' + } + }); + return updatedAssets.map((asset: any) => ({ + id: asset.id, + contract_address: asset.contractAddress, + wallet_address: asset.ownerAddress, + asset_id: asset.assetId, + metadata: asset.metadata ? JSON.parse(asset.metadata) : null, + owner_address: asset.ownerAddress, + tried_count: asset.triedCount, + amount: asset.amount || null, + token_id: asset.tokenId || null, + })); + }, + updateMintingStatusToSubmitted: async (ids: string[]) => { + await client.imAssets.updateMany({ + where: { + id: { + in: ids } - }); - }, - syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => { - // First, attempt to retrieve the existing asset - const existingAsset = await client.imAssets.findUnique({ + }, + data: { + mintingStatus: 'submitted' + } + }); + }, + syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => { + // First, attempt to retrieve the existing asset + const existingAsset = await client.imAssets.findUnique({ + where: { + im_assets_uindex: { + assetId: submittedMintRequest.assetId, + contractAddress: submittedMintRequest.contractAddress, + }, + } + }); + + // Check if the asset exists and the condition for updating is met + if (existingAsset && ( + existingAsset.lastImtblZkevmMintRequestUpdatedId === null + || existingAsset.lastImtblZkevmMintRequestUpdatedId < submittedMintRequest.imtblZkevmMintRequestUpdatedId) + ) { + // Perform update if the existing record's lastImtblZkevmMintRequestUpdatedId is less than the new one or is null + await client.imAssets.update({ where: { im_assets_uindex: { assetId: submittedMintRequest.assetId, contractAddress: submittedMintRequest.contractAddress, }, - } - }); - - // Check if the asset exists and the condition for updating is met - if (existingAsset && ( - existingAsset.lastImtblZkevmMintRequestUpdatedId === null - || existingAsset.lastImtblZkevmMintRequestUpdatedId < submittedMintRequest.imtblZkevmMintRequestUpdatedId) - ) { - // Perform update if the existing record's lastImtblZkevmMintRequestUpdatedId is less than the new one or is null - await client.imAssets.update({ - where: { - im_assets_uindex: { - assetId: submittedMintRequest.assetId, - contractAddress: submittedMintRequest.contractAddress, - }, - }, - data: { - ownerAddress: submittedMintRequest.ownerAddress, - tokenId: submittedMintRequest.tokenId, - mintingStatus: submittedMintRequest.status, - metadataId: submittedMintRequest.metadataId, - lastImtblZkevmMintRequestUpdatedId: submittedMintRequest.imtblZkevmMintRequestUpdatedId, - error: submittedMintRequest.error, - amount: submittedMintRequest.amount || null - } - }); - } else if (!existingAsset) { - // Perform insert if no existing record is found - await client.imAssets.create({ - data: { - assetId: submittedMintRequest.assetId, - contractAddress: submittedMintRequest.contractAddress, - ownerAddress: submittedMintRequest.ownerAddress, - tokenId: submittedMintRequest.tokenId, - mintingStatus: submittedMintRequest.status, - metadataId: submittedMintRequest.metadataId, - lastImtblZkevmMintRequestUpdatedId: submittedMintRequest.imtblZkevmMintRequestUpdatedId, - error: submittedMintRequest.error, - amount: submittedMintRequest.amount || null - } - }); - } - // If existing asset does not meet the update condition, do nothing - }, - updateMintingStatusToSubmissionFailed: async (ids: string[]) => { - await client.imAssets.updateMany({ - where: { - id: { - in: ids - } }, data: { - mintingStatus: 'submission_failed' + ownerAddress: submittedMintRequest.ownerAddress, + tokenId: submittedMintRequest.tokenId, + mintingStatus: submittedMintRequest.status, + metadataId: submittedMintRequest.metadataId, + lastImtblZkevmMintRequestUpdatedId: submittedMintRequest.imtblZkevmMintRequestUpdatedId, + error: submittedMintRequest.error, + amount: submittedMintRequest.amount || null } }); - }, - markAsConflict: async (assetIds: string[], contractAddress: string) => { - await client.imAssets.updateMany({ - where: { - assetId: { - in: assetIds // Targets assets where assetId is in the provided list - }, - contractAddress // Additional condition for contract address - }, + } else if (!existingAsset) { + // Perform insert if no existing record is found + await client.imAssets.create({ data: { - mintingStatus: 'conflicting' // Set the new status + assetId: submittedMintRequest.assetId, + contractAddress: submittedMintRequest.contractAddress, + ownerAddress: submittedMintRequest.ownerAddress, + tokenId: submittedMintRequest.tokenId, + mintingStatus: submittedMintRequest.status, + metadataId: submittedMintRequest.metadataId, + lastImtblZkevmMintRequestUpdatedId: submittedMintRequest.imtblZkevmMintRequestUpdatedId, + error: submittedMintRequest.error, + amount: submittedMintRequest.amount || null } }); - }, - resetMintingStatus: async (ids: string[]) => { - await client.imAssets.updateMany({ - where: { - id: { - in: ids // Condition to match ids - } - }, - data: { - mintingStatus: null // Setting minting_status to null + } + // If existing asset does not meet the update condition, do nothing + }, + updateMintingStatusToSubmissionFailed: async (ids: string[]) => { + await client.imAssets.updateMany({ + where: { + id: { + in: ids } - }); - }, - // this method is not concurrency safe - markForRetry: async (ids: string[]) => { - // Retrieve the current values of tried_count for the specified ids - const assets = await client.imAssets.findMany({ - where: { - id: { - in: ids - } + }, + data: { + mintingStatus: 'submission_failed' + } + }); + }, + markAsConflict: async (assetIds: string[], contractAddress: string) => { + await client.imAssets.updateMany({ + where: { + assetId: { + in: assetIds // Targets assets where assetId is in the provided list }, - select: { - id: true, - triedCount: true // Assuming the field is named triedCount + contractAddress // Additional condition for contract address + }, + data: { + mintingStatus: 'conflicting' // Set the new status + } + }); + }, + resetMintingStatus: async (ids: string[]) => { + await client.imAssets.updateMany({ + where: { + id: { + in: ids // Condition to match ids } - }); - - // Update each asset with the new tried_count and nullify minting_status - for (const asset of assets) { - await client.imAssets.update({ - where: { - id: asset.id - }, - data: { - mintingStatus: null, - triedCount: asset.triedCount + 1 - } - }); + }, + data: { + mintingStatus: null // Setting minting_status to null + } + }); + }, + // this method is not concurrency safe + markForRetry: async (ids: string[]) => { + // Retrieve the current values of tried_count for the specified ids + const assets = await client.imAssets.findMany({ + where: { + id: { + in: ids + } + }, + select: { + id: true, + triedCount: true // Assuming the field is named triedCount } - }, - getMintingRequest: async (contractAddress: string, referenceId: string) => { - // Retrieve the asset from the database based on contract_address and asset_id - const asset = await client.imAssets.findFirst({ + }); + + // Update each asset with the new tried_count and nullify minting_status + for (const asset of assets) { + await client.imAssets.update({ where: { - contractAddress, - assetId: referenceId + id: asset.id + }, + data: { + mintingStatus: null, + triedCount: asset.triedCount + 1 } }); - if (!asset) { - return null; + } + }, + getMintingRequest: async (contractAddress: string, referenceId: string) => { + // Retrieve the asset from the database based on contract_address and asset_id + const asset = await client.imAssets.findFirst({ + where: { + contractAddress, + assetId: referenceId } - return { - asset_id: asset.assetId, - contract_address: asset.contractAddress, - id: asset.id, - metadata: asset.metadata ? JSON.parse(asset.metadata) : null, - owner_address: asset.ownerAddress, - tried_count: asset.triedCount, - wallet_address: asset.ownerAddress, - amount: asset.amount || null - }; + }); + if (!asset) { + return null; } - }; -}; + return { + asset_id: asset.assetId, + contract_address: asset.contractAddress, + id: asset.id, + metadata: asset.metadata ? JSON.parse(asset.metadata) : null, + owner_address: asset.ownerAddress, + tried_count: asset.triedCount, + wallet_address: asset.ownerAddress, + amount: asset.amount || null + }; + } +}); diff --git a/packages/orderbook/package.json b/packages/orderbook/package.json index ff05dfc24a..9bd533ad4c 100644 --- a/packages/orderbook/package.json +++ b/packages/orderbook/package.json @@ -5,7 +5,6 @@ "bugs": "https://github.com/immutable/ts-immutable-sdk/issues", "dependencies": { "@imtbl/config": "workspace:*", - "@imtbl/metrics": "workspace:*", "@opensea/seaport-js": "4.0.3", "axios": "^1.6.5", "ethers": "^6.13.4", diff --git a/packages/orderbook/src/orderbook.ts b/packages/orderbook/src/orderbook.ts index fa4b663d94..34c8f50a42 100644 --- a/packages/orderbook/src/orderbook.ts +++ b/packages/orderbook/src/orderbook.ts @@ -1,5 +1,4 @@ import { ModuleConfiguration } from '@imtbl/config'; -import { track } from '@imtbl/metrics'; import { ImmutableApiClient, ImmutableApiClientFactory } from './api-client'; import { getConfiguredProvider, @@ -385,8 +384,6 @@ export class Orderbook { // contract wallet is not deployed will be an edge case const isSmartContractWallet: boolean = await this.orderbookConfig.provider.getCode(makerAddress) !== '0x'; if (isSmartContractWallet) { - track('orderbookmr', 'bulkListings', { walletType: 'Passport', makerAddress, listingsCount: listingParams.length }); - // eslint-disable-next-line max-len const prepareListingResponses = await Promise.all(listingParams.map((listing) => this.seaport.prepareSeaportOrder( makerAddress, @@ -451,7 +448,6 @@ export class Orderbook { } // Bulk listings (with multiple listings) code path for EOA wallets. - track('orderbookmr', 'bulkListings', { walletType: 'EOA', makerAddress, listingsCount: listingParams.length }); const { actions, preparedOrders } = await this.seaport.prepareBulkSeaportOrders( makerAddress, listingParams.map((listing) => ({ diff --git a/packages/passport/sdk/src/Passport.test.ts b/packages/passport/sdk/src/Passport.test.ts index 4f868476e0..d92f8b1776 100644 --- a/packages/passport/sdk/src/Passport.test.ts +++ b/packages/passport/sdk/src/Passport.test.ts @@ -69,27 +69,21 @@ jest.mock('@imtbl/generated-clients', () => { jest.mock('@imtbl/metrics', () => { const actual = jest.requireActual('@imtbl/metrics'); - const trackErrorMock = jest.fn(); - const trackFlowMock = jest.fn(() => ({ - addEvent: jest.fn(), - details: { flowId: 'flow-id' }, - })); + const trackMock = jest.fn(); return { ...actual, - trackError: trackErrorMock, - trackFlow: trackFlowMock, - setPassportClientId: jest.fn(), + track: trackMock, + configure: jest.fn(), __mocked: { - trackErrorMock, - trackFlowMock, + trackMock, }, }; }); const { __mocked: metricsMocks } = jest.requireMock('@imtbl/metrics'); const { __mocked: walletMocks } = jest.requireMock('@imtbl/wallet'); -const { trackErrorMock } = metricsMocks; +const { trackMock } = metricsMocks; const { connectWalletMock } = walletMocks; describe('Passport', () => { @@ -291,7 +285,7 @@ describe('Passport', () => { await expect(passport.linkExternalWallet(linkWalletParams)).rejects.toThrow( new PassportError('oops', PassportErrorType.LINK_WALLET_ALREADY_LINKED_ERROR), ); - expect(trackErrorMock).toHaveBeenCalledWith('passport', 'linkExternalWallet', error); + expect(trackMock).toHaveBeenCalledWith('passport', 'linkExternalWallet', { error }); }); }); }); diff --git a/packages/passport/sdk/src/Passport.ts b/packages/passport/sdk/src/Passport.ts index 14d295c264..cb519cf598 100644 --- a/packages/passport/sdk/src/Passport.ts +++ b/packages/passport/sdk/src/Passport.ts @@ -3,7 +3,7 @@ import { } from '@imtbl/generated-clients'; import { Environment } from '@imtbl/config'; -import { setPassportClientId, trackError, trackFlow } from '@imtbl/metrics'; +import { configure, track } from '@imtbl/metrics'; import { Auth, UserProfile, @@ -65,7 +65,7 @@ export class Passport { this.multiRollupApiClients = new MultiRollupApiClients(this.passportConfig.multiRollupConfig); this.environment = privateVars.environment; - setPassportClientId(passportModuleConfiguration.clientId); + configure({ clientId: passportModuleConfiguration.clientId }); } // ============================================================================ @@ -145,7 +145,7 @@ export class Passport { }); return provider; - }, 'connectEvm', false); + }, 'connectEvm'); } // ============================================================================ @@ -204,7 +204,7 @@ export class Passport { return withMetricsAsync(async () => { const user = await this.auth.getUser(); return user?.profile; - }, 'getUserInfo', false); + }, 'getUserInfo'); } /** @@ -291,7 +291,7 @@ export class Passport { const headers = { Authorization: `Bearer ${user.accessToken}` }; const getUserInfoResult = await this.multiRollupApiClients.passportProfileApi.getUserInfo({ headers }); return getUserInfoResult.data.linked_addresses; - }, 'getLinkedAddresses', false); + }, 'getLinkedAddresses'); } /** @@ -319,7 +319,7 @@ export class Passport { && 'message' in error ); - const flow = trackFlow('passport', 'linkExternalWallet', false); + track('passport', 'linkExternalWallet'); try { const user = await this.auth.getUser(); @@ -344,9 +344,7 @@ export class Passport { return { ...linkWalletV2Result.data }; } catch (error) { if (error instanceof Error) { - trackError('passport', 'linkExternalWallet', error); - } else { - flow.addEvent('errored'); + track('passport', 'linkExternalWallet', { error }); } if (error instanceof PassportError) { @@ -387,8 +385,6 @@ export class Passport { message, PassportErrorType.LINK_WALLET_GENERIC_ERROR, ); - } finally { - flow.addEvent('End'); } } } diff --git a/packages/passport/sdk/src/types.ts b/packages/passport/sdk/src/types.ts index e550c0ef27..df21f195fb 100644 --- a/packages/passport/sdk/src/types.ts +++ b/packages/passport/sdk/src/types.ts @@ -1,5 +1,4 @@ import { Environment, ModuleConfiguration } from '@imtbl/config'; -import { Flow } from '@imtbl/metrics'; /** * Direct login method identifier @@ -10,10 +9,9 @@ export type DirectLoginMethod = string; export type AccountsRequestedEvent = { environment: Environment; - sendTransaction: (params: Array, flow: Flow) => Promise; + sendTransaction: (params: Array) => Promise; walletAddress: string; passportClient: string; - flow?: Flow; }; export type { diff --git a/packages/passport/sdk/src/utils/metrics.ts b/packages/passport/sdk/src/utils/metrics.ts index 683c9ac717..b9aeecc9ad 100644 --- a/packages/passport/sdk/src/utils/metrics.ts +++ b/packages/passport/sdk/src/utils/metrics.ts @@ -1,29 +1,17 @@ -import { Flow, trackError, trackFlow } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; export const withMetricsAsync = async ( - fn: (flow: Flow) => Promise, + fn: () => Promise, flowName: string, - trackStartEvent: boolean = true, - trackEndEvent: boolean = true, ): Promise => { - const flow: Flow = trackFlow( - 'passport', - flowName, - trackStartEvent, - ); + track('passport', flowName); try { - return await fn(flow); + return await fn(); } catch (error) { if (error instanceof Error) { - trackError('passport', flowName, error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', flowName, { error }); } throw error; - } finally { - if (trackEndEvent) { - flow.addEvent('End'); - } } }; diff --git a/packages/wallet/src/confirmation/confirmation.ts b/packages/wallet/src/confirmation/confirmation.ts index 721e6adb90..7cde2a3128 100644 --- a/packages/wallet/src/confirmation/confirmation.ts +++ b/packages/wallet/src/confirmation/confirmation.ts @@ -1,5 +1,5 @@ import * as GeneratedClients from '@imtbl/generated-clients'; -import { trackError } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import { ConfirmationResult, @@ -198,7 +198,7 @@ export default class ConfirmationScreen { } catch (error) { // If an error is thrown here then the popup is blocked const errorMessage = error instanceof Error ? error.message : String(error); - trackError('passport', 'confirmationPopupDenied', new Error(errorMessage)); + track('passport', 'confirmationPopupDenied', { error: new Error(errorMessage) }); this.overlay = new ConfirmationOverlay(this.config.popupOverlayOptions || {}, true); } diff --git a/packages/wallet/src/linkExternalWallet.ts b/packages/wallet/src/linkExternalWallet.ts index 02a37efbc2..82d508836c 100644 --- a/packages/wallet/src/linkExternalWallet.ts +++ b/packages/wallet/src/linkExternalWallet.ts @@ -1,6 +1,6 @@ import { Auth, isUserZkEvm } from '@imtbl/auth'; import { MultiRollupApiClients } from '@imtbl/generated-clients'; -import { trackFlow, trackError } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import { WalletError, WalletErrorType } from './errors'; import { isAxiosError } from './utils/http'; @@ -72,7 +72,7 @@ export async function linkExternalWallet( apiClient: MultiRollupApiClients, params: LinkWalletParams, ): Promise { - const flowInit = trackFlow('wallet', 'linkExternalWallet'); + track('wallet', 'linkExternalWallet'); try { const user = await auth.getUser(); @@ -102,9 +102,7 @@ export async function linkExternalWallet( } catch (error) { // Track error if (error instanceof Error) { - trackError('wallet', 'linkExternalWallet', error); - } else { - flowInit.addEvent('errored'); + track('wallet', 'linkExternalWallet', { error }); } // Handle and rethrow @@ -141,7 +139,5 @@ export async function linkExternalWallet( message, WalletErrorType.WALLET_CONNECTION_ERROR, ); - } finally { - flowInit.addEvent('End'); } } diff --git a/packages/wallet/src/magic/magicTEESigner.ts b/packages/wallet/src/magic/magicTEESigner.ts index 6efbeab5ac..bb0d58f46c 100644 --- a/packages/wallet/src/magic/magicTEESigner.ts +++ b/packages/wallet/src/magic/magicTEESigner.ts @@ -1,6 +1,6 @@ /* eslint-disable no-bitwise */ import { MagicTeeApiClients } from '@imtbl/generated-clients'; -import { Flow, trackDuration } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import { WalletError, WalletErrorType } from '../errors'; import { withMetricsAsync } from '../utils/metrics'; import { @@ -115,7 +115,7 @@ export default class MagicTEESigner implements WalletSigner { const authenticatedUser = user || await this.getUserOrThrow(); const headers = MagicTEESigner.getHeaders(authenticatedUser); - await withMetricsAsync(async (flow: Flow) => { + await withMetricsAsync(async () => { try { const startTime = performance.now(); // The createWallet endpoint is idempotent, so it can be called multiple times without causing an error. @@ -126,11 +126,9 @@ export default class MagicTEESigner implements WalletSigner { { headers }, ); - trackDuration( - 'passport', - flow.details.flowName, - Math.round(performance.now() - startTime), - ); + track('passport', 'magicCreateWallet', { + durationMs: Math.round(performance.now() - startTime), + }); this.userWallet = { userIdentifier: authenticatedUser.profile.sub, @@ -202,7 +200,7 @@ export default class MagicTEESigner implements WalletSigner { const user = await this.getUserOrThrow(); const headers = MagicTEESigner.getHeaders(user); - return withMetricsAsync(async (flow: Flow) => { + return withMetricsAsync(async () => { try { const startTime = performance.now(); const response = await this.magicTeeApiClient.signOperationsApi.signMessageV1WalletSignMessagePost( @@ -215,11 +213,9 @@ export default class MagicTEESigner implements WalletSigner { { headers }, ); - trackDuration( - 'passport', - flow.details.flowName, - Math.round(performance.now() - startTime), - ); + track('passport', 'magicSignMessage', { + durationMs: Math.round(performance.now() - startTime), + }); return response.data.signature as `0x${string}`; } catch (error) { diff --git a/packages/wallet/src/types.ts b/packages/wallet/src/types.ts index b51799fdaf..93fd1e079f 100644 --- a/packages/wallet/src/types.ts +++ b/packages/wallet/src/types.ts @@ -1,4 +1,3 @@ -import { Flow } from '@imtbl/metrics'; import { TypedEventEmitter, User } from '@imtbl/auth'; import { JsonRpcError } from './zkEvm/JsonRpcError'; @@ -29,10 +28,9 @@ export enum WalletEvents { export type AccountsRequestedEvent = { sessionActivityApiUrl: string; - sendTransaction: (params: Array, flow: Flow) => Promise; + sendTransaction: (params: Array) => Promise; walletAddress: string; passportClient: string; - flow?: Flow; }; // WalletEventMap for internal wallet events diff --git a/packages/wallet/src/utils/metrics.ts b/packages/wallet/src/utils/metrics.ts index 3198a51d09..e79bfda60a 100644 --- a/packages/wallet/src/utils/metrics.ts +++ b/packages/wallet/src/utils/metrics.ts @@ -1,57 +1,31 @@ -import { Flow, trackError, trackFlow } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; export const withMetrics = ( - fn: (flow: Flow) => T, + fn: () => T, flowName: string, - trackStartEvent: boolean = true, - trackEndEvent: boolean = true, ): T => { - const flow: Flow = trackFlow( - 'passport', - flowName, - trackStartEvent, - ); - + track('passport', flowName); try { - return fn(flow); + return fn(); } catch (error) { if (error instanceof Error) { - trackError('passport', flowName, error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', flowName, { error }); } throw error; - } finally { - if (trackEndEvent) { - flow.addEvent('End'); - } } }; export const withMetricsAsync = async ( - fn: (flow: Flow) => Promise, + fn: () => Promise, flowName: string, - trackStartEvent: boolean = true, - trackEndEvent: boolean = true, ): Promise => { - const flow: Flow = trackFlow( - 'passport', - flowName, - trackStartEvent, - ); - + track('passport', flowName); try { - return await fn(flow); + return await fn(); } catch (error) { if (error instanceof Error) { - trackError('passport', flowName, error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', flowName, { error }); } throw error; - } finally { - if (trackEndEvent) { - flow.addEvent('End'); - } } }; diff --git a/packages/wallet/src/zkEvm/personalSign.ts b/packages/wallet/src/zkEvm/personalSign.ts index dbe8c6949d..8e1f8c88ba 100644 --- a/packages/wallet/src/zkEvm/personalSign.ts +++ b/packages/wallet/src/zkEvm/personalSign.ts @@ -1,4 +1,3 @@ -import { Flow } from '@imtbl/metrics'; import type { PublicClient } from 'viem'; import { JsonRpcError, RpcErrorCode } from './JsonRpcError'; import { hexToString } from '../utils/string'; @@ -14,7 +13,6 @@ interface PersonalSignParams { zkEvmAddress: string; guardianClient: GuardianClient; relayerClient: RelayerClient; - flow: Flow; } export const personalSign = async ({ @@ -24,7 +22,6 @@ export const personalSign = async ({ rpcProvider, guardianClient, relayerClient, - flow, }: PersonalSignParams): Promise => { const message: string = params[0]; const fromAddress: string = params[1]; @@ -40,24 +37,19 @@ export const personalSign = async ({ // Convert message into a string if it's a hex const payload = hexToString(message); const chainId = await rpcProvider.getChainId(); - flow.addEvent('endDetectNetwork'); const chainIdBigNumber = BigInt(chainId); // Sign the message with the EOA without blocking const eoaSignaturePromise = signERC191Message(chainIdBigNumber, payload, ethSigner, fromAddress); - eoaSignaturePromise.then(() => flow.addEvent('endEOASignature')); await guardianClient.evaluateERC191Message({ chainID: chainIdBigNumber, payload }); - flow.addEvent('endEvaluateERC191Message'); const [eoaSignature, relayerSignature] = await Promise.all([ eoaSignaturePromise, relayerClient.imSign(fromAddress, payload), ]); - flow.addEvent('endRelayerSign'); const eoaAddress = await ethSigner.getAddress(); - flow.addEvent('endGetEOAAddress'); return packSignatures(eoaSignature, eoaAddress, relayerSignature); }; diff --git a/packages/wallet/src/zkEvm/sendDeployTransactionAndPersonalSign.ts b/packages/wallet/src/zkEvm/sendDeployTransactionAndPersonalSign.ts index f769232b0f..8ece0895c8 100644 --- a/packages/wallet/src/zkEvm/sendDeployTransactionAndPersonalSign.ts +++ b/packages/wallet/src/zkEvm/sendDeployTransactionAndPersonalSign.ts @@ -12,7 +12,6 @@ export const sendDeployTransactionAndPersonalSign = async ({ relayerClient, guardianClient, zkEvmAddress, - flow, }: EthSendDeployTransactionParams): Promise => { const deployTransaction = { to: zkEvmAddress, value: 0n }; @@ -23,7 +22,6 @@ export const sendDeployTransactionAndPersonalSign = async ({ guardianClient, relayerClient, zkEvmAddress, - flow, }); return guardianClient.withConfirmationScreen()(async () => { @@ -34,10 +32,9 @@ export const sendDeployTransactionAndPersonalSign = async ({ rpcProvider, guardianClient, relayerClient, - flow, }); - await pollRelayerTransaction(relayerClient, relayerId, flow); + await pollRelayerTransaction(relayerClient, relayerId); return signedMessage; }); diff --git a/packages/wallet/src/zkEvm/sendTransaction.ts b/packages/wallet/src/zkEvm/sendTransaction.ts index 60068346bc..fd4768e9bd 100644 --- a/packages/wallet/src/zkEvm/sendTransaction.ts +++ b/packages/wallet/src/zkEvm/sendTransaction.ts @@ -11,7 +11,6 @@ export const sendTransaction = async ({ relayerClient, guardianClient, zkEvmAddress, - flow, nonceSpace, isBackgroundTransaction = false, }: EthSendTransactionParams): Promise => { @@ -24,11 +23,10 @@ export const sendTransaction = async ({ guardianClient, relayerClient, zkEvmAddress, - flow, nonceSpace, isBackgroundTransaction, }); - const { hash } = await pollRelayerTransaction(relayerClient, relayerId, flow); + const { hash } = await pollRelayerTransaction(relayerClient, relayerId); return hash; }; diff --git a/packages/wallet/src/zkEvm/sessionActivity/errorBoundary.ts b/packages/wallet/src/zkEvm/sessionActivity/errorBoundary.ts index 918debe8bb..25b2921658 100644 --- a/packages/wallet/src/zkEvm/sessionActivity/errorBoundary.ts +++ b/packages/wallet/src/zkEvm/sessionActivity/errorBoundary.ts @@ -1,4 +1,4 @@ -import { trackError } from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; export function errorBoundary any>( @@ -15,7 +15,7 @@ export function errorBoundary { if (error instanceof Error) { - trackError('passport', 'sessionActivityError', error); + track('passport', 'sessionActivityError', { error }); } return fallbackResult; }); @@ -24,7 +24,7 @@ export function errorBoundary new Promise((resolve) => { }); const trackSessionActivityFn = async (args: AccountsRequestedEvent) => { - // Use an existing flow if one is provided, or create a new one - const flow = args.flow || trackFlow('passport', 'sendSessionActivity'); + track('passport', 'sendSessionActivity'); const clientId = args.passportClient; if (!clientId) { - flow.addEvent('No Passport Client ID'); throw new Error('No Passport Client ID provided'); } // If there is already a tracking call in progress, do nothing @@ -77,7 +74,6 @@ const trackSessionActivityFn = async (args: AccountsRequestedEvent) => { const from = args.walletAddress; if (!from) { - flow.addEvent('No Passport Wallet Address'); throw new Error('No wallet address'); } // Return type of get @@ -97,7 +93,6 @@ const trackSessionActivityFn = async (args: AccountsRequestedEvent) => { return; } } catch (error) { - flow.addEvent('Failed to fetch details'); throw new Error('Failed to get details', { cause: error }); } @@ -112,26 +107,21 @@ const trackSessionActivityFn = async (args: AccountsRequestedEvent) => { // If transaction payload, send transaction try { - flow.addEvent('Start Sending Transaction'); - const tx = await args.sendTransaction([{ to, from, data }], flow); + await args.sendTransaction([{ to, from, data }]); incrementSendCount(clientId); - flow.addEvent('Transaction Sent', { tx }); } catch (error) { - flow.addEvent('Failed to send Transaction'); const err = new Error('Failed to send transaction', { cause: error }); - trackError('passport', 'sessionActivityError', err, { flowId: flow.details.flowId }); + track('passport', 'sessionActivityError', { error: err }); } } // if delay, perform delay. if (details && details.delay && details.delay > 0) { - flow.addEvent('Delaying Transaction', { delay: details.delay }); await wait(details.delay); setTimeout(() => { - flow.addEvent('Retrying after Delay'); currentSessionTrackCall[clientId] = false; // eslint-disable-next-line - trackSessionWrapper({ ...args, flow }); + trackSessionWrapper(args); }, 0); } }; diff --git a/packages/wallet/src/zkEvm/sessionActivity/storage.ts b/packages/wallet/src/zkEvm/sessionActivity/storage.ts new file mode 100644 index 0000000000..f816927e77 --- /dev/null +++ b/packages/wallet/src/zkEvm/sessionActivity/storage.ts @@ -0,0 +1,33 @@ +/** + * Minimal namespaced localStorage helper for session activity counters. + * Uses the same `__IMX-` prefix as the former metrics utils export. + */ + +const PREFIX = '__IMX-'; + +const hasLocalStorage = () => typeof window !== 'undefined' && !!window.localStorage; + +const parseItem = (payload: string | null) => { + if (payload === null) return undefined; + try { + return JSON.parse(payload); + } catch { + return payload; + } +}; + +export function getItem(key: string): T | undefined { + if (!hasLocalStorage()) { + return undefined; + } + return parseItem(window.localStorage.getItem(`${PREFIX}${key}`)) as T | undefined; +} + +export const setItem = (key: string, payload: unknown): boolean => { + if (!hasLocalStorage()) { + return false; + } + const value = typeof payload === 'string' ? payload : JSON.stringify(payload); + window.localStorage.setItem(`${PREFIX}${key}`, value); + return true; +}; diff --git a/packages/wallet/src/zkEvm/signEjectionTransaction.ts b/packages/wallet/src/zkEvm/signEjectionTransaction.ts index d228bcc76a..b113c96c5d 100644 --- a/packages/wallet/src/zkEvm/signEjectionTransaction.ts +++ b/packages/wallet/src/zkEvm/signEjectionTransaction.ts @@ -14,7 +14,6 @@ export const signEjectionTransaction = async ({ params, ethSigner, zkEvmAddress, - flow, }: EthSendTransactionEjectionParams): Promise => { if (!params || params.length !== 1) { throw new JsonRpcError( @@ -28,6 +27,5 @@ export const signEjectionTransaction = async ({ transactionRequest, ethSigner, zkEvmAddress, - flow, }); }; diff --git a/packages/wallet/src/zkEvm/signTypedDataV4.ts b/packages/wallet/src/zkEvm/signTypedDataV4.ts index 371624a494..d9c16399a6 100644 --- a/packages/wallet/src/zkEvm/signTypedDataV4.ts +++ b/packages/wallet/src/zkEvm/signTypedDataV4.ts @@ -1,4 +1,3 @@ -import { Flow } from '@imtbl/metrics'; import type { PublicClient } from 'viem'; import GuardianClient from '../guardian'; import { signAndPackTypedData } from './walletHelpers'; @@ -14,7 +13,6 @@ export type SignTypedDataV4Params = { method: string; params: Array; guardianClient: GuardianClient; - flow: Flow; }; const REQUIRED_TYPED_DATA_PROPERTIES = ['types', 'domain', 'primaryType', 'message']; @@ -77,7 +75,6 @@ export const signTypedDataV4 = async ({ rpcProvider, relayerClient, guardianClient, - flow, }: SignTypedDataV4Params): Promise => { const fromAddress: string = params[0]; const typedDataParam: string | object = params[1]; @@ -88,13 +85,10 @@ export const signTypedDataV4 = async ({ const chainId = await rpcProvider.getChainId(); const typedData = transformTypedData(typedDataParam, BigInt(chainId)); - flow.addEvent('endDetectNetwork'); await guardianClient.evaluateEIP712Message({ chainID: String(chainId), payload: typedData }); - flow.addEvent('endValidateMessage'); const relayerSignature = await relayerClient.imSignTypedData(fromAddress, typedData); - flow.addEvent('endRelayerSignTypedData'); const signature = await signAndPackTypedData( typedData, @@ -103,7 +97,6 @@ export const signTypedDataV4 = async ({ fromAddress, ethSigner, ); - flow.addEvent('getSignedTypedData'); return signature; }; diff --git a/packages/wallet/src/zkEvm/transactionHelpers.ts b/packages/wallet/src/zkEvm/transactionHelpers.ts index 0c5071243a..f1528600d0 100644 --- a/packages/wallet/src/zkEvm/transactionHelpers.ts +++ b/packages/wallet/src/zkEvm/transactionHelpers.ts @@ -1,4 +1,3 @@ -import { Flow } from '@imtbl/metrics'; import type { PublicClient, Hex } from 'viem'; import { getEip155ChainId, @@ -38,12 +37,11 @@ export type TransactionParams = { guardianClient: GuardianClient; relayerClient: RelayerClient; zkEvmAddress: string; - flow: Flow; nonceSpace?: bigint; isBackgroundTransaction?: boolean; }; -export type EjectionTransactionParams = Pick; +export type EjectionTransactionParams = Pick; export type EjectionTransactionResponse = { to: string; data: string; @@ -136,7 +134,6 @@ const buildMetaTransactions = async ( export const pollRelayerTransaction = async ( relayerClient: RelayerClient, relayerId: string, - flow: Flow, ) => { const retrieveRelayerTransaction = async () => { const tx = await relayerClient.imGetTransactionByHash(relayerId); @@ -158,7 +155,6 @@ export const pollRelayerTransaction = async ( 'transaction hash not generated in time', ), }); - flow.addEvent('endRetrieveRelayerTransaction'); if ( ![ @@ -183,13 +179,11 @@ export const prepareAndSignTransaction = async ({ guardianClient, relayerClient, zkEvmAddress, - flow, nonceSpace, isBackgroundTransaction, }: TransactionParams & { transactionRequest: TransactionRequest }) => { const chainId = await rpcProvider.getChainId(); const chainIdBigNumber = BigInt(chainId); - flow.addEvent('endDetectNetwork'); const metaTransactions = await buildMetaTransactions( transactionRequest, @@ -198,7 +192,6 @@ export const prepareAndSignTransaction = async ({ zkEvmAddress, nonceSpace, ); - flow.addEvent('endBuildMetaTransactions'); const { nonce } = metaTransactions[0]; if (typeof nonce === 'undefined') { @@ -214,7 +207,6 @@ export const prepareAndSignTransaction = async ({ metaTransactions, isBackgroundTransaction, }); - flow.addEvent('endValidateEVMTransaction'); }; // NOTE: We sign again because we now are adding the fee transaction, so the @@ -227,7 +219,6 @@ export const prepareAndSignTransaction = async ({ zkEvmAddress, ethSigner, ); - flow.addEvent('endGetSignedMetaTransactions'); return signed; }; @@ -237,7 +228,6 @@ export const prepareAndSignTransaction = async ({ ]); const relayerId = await relayerClient.ethSendTransaction(zkEvmAddress, signedTransactions); - flow.addEvent('endRelayerSendTransaction'); return { signedTransactions, relayerId, nonce }; }; @@ -281,12 +271,10 @@ export const prepareAndSignEjectionTransaction = async ({ transactionRequest, ethSigner, zkEvmAddress, - flow, }: EjectionTransactionParams & { transactionRequest: TransactionRequest }): Promise => { const metaTransaction = await buildMetaTransactionForEjection( transactionRequest, ); - flow.addEvent('endBuildMetaTransactions'); const signedTransaction = await signMetaTransactions( metaTransaction, @@ -295,7 +283,6 @@ export const prepareAndSignEjectionTransaction = async ({ zkEvmAddress, ethSigner, ); - flow.addEvent('endGetSignedMetaTransactions'); return { to: zkEvmAddress, diff --git a/packages/wallet/src/zkEvm/user/registerZkEvmUser.ts b/packages/wallet/src/zkEvm/user/registerZkEvmUser.ts index c9bc3433cb..0d9a3422a5 100644 --- a/packages/wallet/src/zkEvm/user/registerZkEvmUser.ts +++ b/packages/wallet/src/zkEvm/user/registerZkEvmUser.ts @@ -1,5 +1,4 @@ import { MultiRollupApiClients } from '@imtbl/generated-clients'; -import { Flow } from '@imtbl/metrics'; import type { PublicClient } from 'viem'; import { getEip155ChainId } from '../walletHelpers'; import { JsonRpcError, RpcErrorCode } from '../JsonRpcError'; @@ -16,7 +15,6 @@ export type RegisterZkEvmUserInput = { multiRollupApiClients: MultiRollupApiClients, accessToken: string; rpcProvider: PublicClient; - flow: Flow; }; const MESSAGE_TO_SIGN = 'Only sign this message from Immutable Passport'; @@ -27,20 +25,12 @@ export async function registerZkEvmUser({ multiRollupApiClients, accessToken, rpcProvider, - flow, }: RegisterZkEvmUserInput): Promise { // Parallelize the operations that can happen concurrently const getAddressPromise = ethSigner.getAddress(); - getAddressPromise.then(() => flow.addEvent('endGetAddress')); - const signRawPromise = signRaw(MESSAGE_TO_SIGN, ethSigner); - signRawPromise.then(() => flow.addEvent('endSignRaw')); - const detectNetworkPromise = rpcProvider.getChainId(); - detectNetworkPromise.then(() => flow.addEvent('endDetectNetwork')); - const listChainsPromise = multiRollupApiClients.chainsApi.listChains(); - listChainsPromise.then(() => flow.addEvent('endListChains')); const [ethereumAddress, ethereumSignature, chainId, chainListResponse] = await Promise.all([ getAddressPromise, @@ -68,7 +58,6 @@ export async function registerZkEvmUser({ }, { headers: { Authorization: `Bearer ${accessToken}` }, }); - flow.addEvent('endCreateCounterfactualAddress'); // Trigger a background refresh to get the updated user with zkEvm info. // This is a best-effort operation - the caller will need to get updated diff --git a/packages/wallet/src/zkEvm/zkEvmProvider.ts b/packages/wallet/src/zkEvm/zkEvmProvider.ts index fa90d1b3bf..74d92f07cc 100644 --- a/packages/wallet/src/zkEvm/zkEvmProvider.ts +++ b/packages/wallet/src/zkEvm/zkEvmProvider.ts @@ -1,7 +1,5 @@ import { MultiRollupApiClients } from '@imtbl/generated-clients'; -import { - Flow, identify, trackError, trackFlow, -} from '@imtbl/metrics'; +import { track } from '@imtbl/metrics'; import { createPublicClient, http, @@ -165,14 +163,13 @@ export class ZkEvmProvider implements Provider { // we can submit a session activity request per SCW in parallel without a SCW // INVALID_NONCE error. const nonceSpace: bigint = BigInt(1); - const sendTransactionClosure = async (params: Array, flow: Flow) => await sendTransaction({ + const sendTransactionClosure = async (params: Array) => await sendTransaction({ params, ethSigner: this.#ethSigner, guardianClient: this.#guardianClient, rpcProvider: this.#rpcProvider, relayerClient: this.#relayerClient, zkEvmAddress, - flow, nonceSpace, isBackgroundTransaction: true, }); @@ -206,7 +203,7 @@ export class ZkEvmProvider implements Provider { const zkEvmAddress = await this.#getZkEvmAddress(); if (zkEvmAddress) return [zkEvmAddress]; - const flow = trackFlow('passport', 'ethRequestAccounts'); + track('passport', 'ethRequestAccounts'); try { // Get user via getUser function @@ -217,27 +214,21 @@ export class ZkEvmProvider implements Provider { 'User not authenticated. Please log in first.', ); } - flow.addEvent('endGetUser'); let userZkEvmEthAddress: string | undefined; if (!isZkEvmUser(user)) { - flow.addEvent('startUserRegistration'); - userZkEvmEthAddress = await registerZkEvmUser({ ethSigner: this.#ethSigner, getUser: this.#getUser, multiRollupApiClients: this.#multiRollupApiClients, accessToken: user.accessToken, rpcProvider: this.#rpcProvider, - flow, }); - flow.addEvent('endUserRegistration'); // Force refresh to update session with zkEvm claims from IDP // This ensures subsequent getUser() calls return the updated user await this.#getUser(true); - flow.addEvent('endForceRefresh'); } else { userZkEvmEthAddress = user.zkEvm.ethAddress; } @@ -245,20 +236,13 @@ export class ZkEvmProvider implements Provider { this.#providerEventEmitter.emit(ProviderEvent.ACCOUNTS_CHANGED, [ userZkEvmEthAddress, ]); - identify({ - passportId: user.profile.sub, - }); this.#callSessionActivity(userZkEvmEthAddress); return [userZkEvmEthAddress]; } catch (error) { if (error instanceof Error) { - trackError('passport', 'ethRequestAccounts', error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', 'ethRequestAccounts', { error }); } throw error; - } finally { - flow.addEvent('End'); } } case 'eth_sendTransaction': { @@ -270,7 +254,7 @@ export class ZkEvmProvider implements Provider { ); } - const flow = trackFlow('passport', 'ethSendTransaction'); + track('passport', 'ethSendTransaction'); try { return await this.#guardianClient.withConfirmationScreen({ @@ -283,17 +267,12 @@ export class ZkEvmProvider implements Provider { rpcProvider: this.#rpcProvider, relayerClient: this.#relayerClient, zkEvmAddress, - flow, })); } catch (error) { if (error instanceof Error) { - trackError('passport', 'eth_sendTransaction', error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', 'eth_sendTransaction', { error }); } throw error; - } finally { - flow.addEvent('End'); } } case 'eth_accounts': { @@ -309,7 +288,7 @@ export class ZkEvmProvider implements Provider { ); } - const flow = trackFlow('passport', 'personalSign'); + track('passport', 'personalSign'); try { return await this.#guardianClient.withConfirmationScreen({ @@ -329,7 +308,6 @@ export class ZkEvmProvider implements Provider { rpcProvider: this.#rpcProvider, guardianClient: this.#guardianClient, relayerClient: this.#relayerClient, - flow, }); } } @@ -341,18 +319,13 @@ export class ZkEvmProvider implements Provider { rpcProvider: this.#rpcProvider, guardianClient: this.#guardianClient, relayerClient: this.#relayerClient, - flow, }); }); } catch (error) { if (error instanceof Error) { - trackError('passport', 'personal_sign', error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', 'personal_sign', { error }); } throw error; - } finally { - flow.addEvent('End'); } } case 'eth_signTypedData': @@ -365,7 +338,7 @@ export class ZkEvmProvider implements Provider { ); } - const flow = trackFlow('passport', 'ethSignTypedDataV4'); + track('passport', 'ethSignTypedDataV4'); try { return await this.#guardianClient.withConfirmationScreen({ @@ -378,17 +351,12 @@ export class ZkEvmProvider implements Provider { rpcProvider: this.#rpcProvider, relayerClient: this.#relayerClient, guardianClient: this.#guardianClient, - flow, })); } catch (error) { if (error instanceof Error) { - trackError('passport', 'eth_signTypedData', error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', 'eth_signTypedData', { error }); } throw error; - } finally { - flow.addEvent('End'); } } case 'eth_chainId': { @@ -441,24 +409,19 @@ export class ZkEvmProvider implements Provider { ); } - const flow = trackFlow('passport', 'imSignEjectionTransaction'); + track('passport', 'imSignEjectionTransaction'); try { return await signEjectionTransaction({ params: request.params || [], ethSigner: this.#ethSigner, zkEvmAddress, - flow, }); } catch (error) { if (error instanceof Error) { - trackError('passport', 'imSignEjectionTransaction', error, { flowId: flow.details.flowId }); - } else { - flow.addEvent('errored'); + track('passport', 'imSignEjectionTransaction', { error }); } throw error; - } finally { - flow.addEvent('End'); } } case 'im_addSessionActivity': { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 693ed74d9f..5a3cd1f4f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,13 +120,13 @@ importers: version: 29.7.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2)) ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2)))(typescript@5.6.2) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2) + version: 10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2) typescript: specifier: ^5 version: 5.6.2 @@ -138,7 +138,7 @@ importers: version: 0.25.21(@emotion/react@11.11.3(@types/react@18.3.12)(react@18.3.1))(@rive-app/react-canvas-lite@4.9.0(react@18.3.1))(embla-carousel-react@8.1.5(react@18.3.1))(framer-motion@11.18.2(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@imtbl/sdk': specifier: latest - version: 2.20.1(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10) + version: 2.22.0(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10) next: specifier: 14.2.25 version: 14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -940,7 +940,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -971,7 +971,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -1014,7 +1014,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -1026,7 +1026,7 @@ importers: version: 6.4.1(rollup@4.28.0)(typescript@5.6.2) ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2))(typescript@5.6.2) tsup: specifier: ^8.3.0 version: 8.3.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(jiti@1.21.0)(postcss@8.4.49)(typescript@5.6.2)(yaml@2.5.0) @@ -1121,7 +1121,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) next: specifier: ^15.2.6 version: 15.5.10(@babel/core@7.26.10)(@playwright/test@1.60.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1157,7 +1157,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) next: specifier: ^15.2.6 version: 15.5.10(@babel/core@7.26.10)(@playwright/test@1.60.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -1197,7 +1197,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -1225,9 +1225,6 @@ importers: '@imtbl/generated-clients': specifier: workspace:* version: link:../../internal/generated-clients - '@imtbl/metrics': - specifier: workspace:* - version: link:../../internal/metrics '@imtbl/orderbook': specifier: workspace:* version: link:../../orderbook @@ -1276,7 +1273,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -1373,7 +1370,7 @@ importers: version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) react-scripts: specifier: 5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) + version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) typescript: specifier: ^5.6.2 version: 5.6.2 @@ -1539,7 +1536,7 @@ importers: version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) react-scripts: specifier: 5.0.1 - version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) + version: 5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1551,7 +1548,7 @@ importers: version: 0.13.0(rollup@4.28.0) ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)))(typescript@5.6.2) typescript: specifier: ^5.6.2 version: 5.6.2 @@ -1648,10 +1645,6 @@ importers: version: 2.1.4 packages/config: - dependencies: - '@imtbl/metrics': - specifier: workspace:* - version: link:../internal/metrics devDependencies: '@swc/core': specifier: ^1.4.2 @@ -1976,7 +1969,7 @@ importers: version: 22.19.7 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1988,13 +1981,6 @@ importers: version: 5.6.2 packages/internal/metrics: - dependencies: - global-const: - specifier: ^0.1.2 - version: 0.1.2 - lru-memorise: - specifier: 0.3.0 - version: 0.3.0 devDependencies: '@swc/core': specifier: ^1.4.2 @@ -2013,13 +1999,13 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2))(typescript@5.6.2) tsup: specifier: ^8.3.0 version: 8.3.0(@swc/core@1.15.3(@swc/helpers@0.5.15))(jiti@1.21.0)(postcss@8.4.49)(typescript@5.6.2)(yaml@2.5.0) @@ -2111,9 +2097,6 @@ importers: '@imtbl/generated-clients': specifier: workspace:* version: link:../../internal/generated-clients - '@imtbl/metrics': - specifier: workspace:* - version: link:../../internal/metrics '@imtbl/webhook': specifier: workspace:* version: link:../../webhook/sdk @@ -2151,7 +2134,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -2170,9 +2153,6 @@ importers: '@imtbl/config': specifier: workspace:* version: link:../config - '@imtbl/metrics': - specifier: workspace:* - version: link:../internal/metrics '@opensea/seaport-js': specifier: 4.0.3 version: 4.0.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -2209,7 +2189,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -2492,7 +2472,7 @@ importers: version: 8.57.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + version: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-environment-jsdom: specifier: ^29.4.3 version: 29.6.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -4431,8 +4411,8 @@ packages: cpu: [x64] os: [win32] - '@imtbl/auth-next-client@2.20.1': - resolution: {integrity: sha512-spoCdsUVGhwkNFXp1RoSPoHVHJgxR1tSh4nSdkCYd4m/b8q6n2PqtfP+wPjsGlBTDVgUu/JJd7/0XncydizS4w==} + '@imtbl/auth-next-client@2.22.0': + resolution: {integrity: sha512-tenOYDpycmRMoW7jRREYTjkCMKuNUBRTz0QaZ3KGPqSaR03boq2Vy/zEk3spCA0pNKEUQ2DgK3BJefgjsHXbaQ==} peerDependencies: next: ^14.0.0 || ^15.0.0 next-auth: ^5.0.0-beta.25 @@ -4445,8 +4425,8 @@ packages: react: optional: true - '@imtbl/auth-next-server@2.20.1': - resolution: {integrity: sha512-gixsLtJLu51x3lC9mDdql+cyi7VpQPoVovBUj2rra9+iVrM1qNE1gfQuiO/yYiF51oDZv9q8O5m4uRkZsZNQcw==} + '@imtbl/auth-next-server@2.22.0': + resolution: {integrity: sha512-RQm2ovzifNXVK5wWyBGVwzLseRMl8DXOy/gUPd0B8Xlm41GBZXF95ftWvahUjcYb3RTX3/AMLSzPDi/kybb/dA==} peerDependencies: next: ^14.0.0 || ^15.0.0 next-auth: ^5.0.0-beta.25 @@ -4456,21 +4436,21 @@ packages: next-auth: optional: true - '@imtbl/auth@2.20.1': - resolution: {integrity: sha512-7pxe15/AsoV24ot5K6oNETU1exUW4+4iU1oDKKVoDtu9OvXyFaK1xPmAWevaAj0Bm1QNTxY6iY6Ot14EmZrKAg==} + '@imtbl/auth@2.22.0': + resolution: {integrity: sha512-e9gh2Ke20NKhzOar7fLYQuidZivgKW/aIOeFxsVrlmOrcr0QzCZeUqTHLEW7apA0nN37q8ucI7FnRfCqm4YJPA==} - '@imtbl/blockchain-data@2.20.1': - resolution: {integrity: sha512-7k1PTXt8TPU6MEis8gvrKZod5eIgCzj7d/CwEwx05pDXFOtb+EMUQS3wwmA/Tcigt4o84jU4OLTKuP8/LfmUYQ==} + '@imtbl/blockchain-data@2.22.0': + resolution: {integrity: sha512-PQkAD20F5GuAj+AhI/ukp5kgBEPYnVhGuFwpWqQwjWQr+xh5leI5/eT4x4ATSI/4snYfOZDwYBbSyaNGNS+U/A==} - '@imtbl/bridge-sdk@2.20.1': - resolution: {integrity: sha512-4IX/UFIk4QKd8FntP3E1QSptkg796Vjw6eue+NqMJIt1sdkFhqep+NBmLST2vIPxPMR7//+IkZYvz8JpjYzaFA==} + '@imtbl/bridge-sdk@2.22.0': + resolution: {integrity: sha512-i/WUd+o5OZtMg4d6l/wTSCornS7GYkDmRIlVCBC/hNmvewg7BLOT4+PpJKVyx6rszN1o2gmfwtHiJoeuB9OIrA==} engines: {node: '>=20.11.0'} - '@imtbl/checkout-sdk@2.20.1': - resolution: {integrity: sha512-9B26rMEM44QfG7d6wjNq2jNFx+4+9YhR6KOv2G4WEdSb/oh9rvUIJGHtsdBSVszE7k9nDfvK6w16jb520YQBig==} + '@imtbl/checkout-sdk@2.22.0': + resolution: {integrity: sha512-EaZNKjKDbNeU3XK13pv49XAc49gGc11v8oXKx/4CSEXvUmL53etVZYWcDOwUKA7czoIpKyCHsuHIku0YlOH5QQ==} - '@imtbl/config@2.20.1': - resolution: {integrity: sha512-P7qRLFLG/WbFC2veFfYCGN0GtTQ/6J71UDwa0ST/QW00s2swyAUNS54ttHFQnepphimbOTilgUpc/O/VANnbAg==} + '@imtbl/config@2.22.0': + resolution: {integrity: sha512-uyJKu1bhSLFIs2eDIgwyB++6rcXzWBjS9AUUVl4dh2PcZP1QqV7NxWwbNK4Eu0i3HAJVwPS+epzN3jPeDmSKPQ==} engines: {node: '>=20.11.0'} '@imtbl/contracts@2.2.6': @@ -4482,36 +4462,36 @@ packages: peerDependencies: '@openzeppelin/contracts': ^4.9.3 || ^5.0.0 - '@imtbl/dex-sdk@2.20.1': - resolution: {integrity: sha512-vfdcpSoSzujRAhfJzgdf0q9v3ewlANEFiWqsZvECVhpdoP2Roo5KhXL3A+cHtyIjQU62gqxKgN5knkdlcoASPQ==} + '@imtbl/dex-sdk@2.22.0': + resolution: {integrity: sha512-hieaT7AHOdGNiXHjNLKkKQsEtaGs90ip6T77VpwctbxCjk0BOVJ07mkjinLFMw5FeYQAZBXx1Rxzs30w6SB2lw==} engines: {node: '>=20.11.0'} - '@imtbl/generated-clients@2.20.1': - resolution: {integrity: sha512-pqZHyjjJZwNkPYOtQynO3I/zgq4Ed71F0hnGaw2F+9VyvWFP7+rNU4rTMliS0dNaNhPMpIH9OFBbxyBa2BSYOw==} + '@imtbl/generated-clients@2.22.0': + resolution: {integrity: sha512-Qw0DOAruDlxFu+lVONpsjLrkel2Qdsy2vZAEI/futVfynSjeKMKZ01sGl+ADOm6RZkny9uKWXk7U/bUwotM7DA==} engines: {node: '>=20.11.0'} '@imtbl/image-resizer-utils@0.0.3': resolution: {integrity: sha512-/EOJKMJF4gD/Dv0qNhpUTpp2AgWeQ7XgYK9Xjl+xP2WWgaarNv1SHe1aeqSb8aZT5W7wSXdUGzn6TIxNsuCWGw==} - '@imtbl/metrics@2.20.1': - resolution: {integrity: sha512-kugCCz8e1oWTeE5T0yh2DmcgDGSUY4zO9TgbJsiRnbB8H86D7sPQEiMPpt+cmDUEEh4Xdlgz2oD9jxtLpPVYWQ==} + '@imtbl/metrics@2.22.0': + resolution: {integrity: sha512-OrY+MVI1CyurNNTpDL7ePa8bL1srR8q8p+C53Y9ixX7jcrfG6issZtDHDYxUiYc/Lw+fswfV2od5E7S3vZVoSg==} engines: {node: '>=20.11.0'} - '@imtbl/minting-backend@2.20.1': - resolution: {integrity: sha512-H2qpQjg5BRq1UG025oP5do0aoM90Z4XWK49UrVSzIDO3pBGELMoWNtI61N2hFZAPSBYsiFX0r4AgNd5FeUlaZA==} + '@imtbl/minting-backend@2.22.0': + resolution: {integrity: sha512-SIVNaApi4vvMyWV/N5XqTjkv1P1+C54tdfv724eUTNp6ZIBp8uMhKxM4cbddPQOinnVtdz09mNa4SX4Yydro8Q==} - '@imtbl/orderbook@2.20.1': - resolution: {integrity: sha512-oflTDkOFXAGYZgFFsIWvVUJhxEYDSl8zLtVUp+q3c716PPIAT5Vi8PbkWTolIp2DvVzzjNL3EviYRVSKrRkkgQ==} + '@imtbl/orderbook@2.22.0': + resolution: {integrity: sha512-fYKbQt3And9tLoUrLjsxG3bWzCw6JDQw7c9Az+sOUY8ENdqDpmE1VmwktGgwOBm9VTmRFYyk7r/rioj9M1d3yw==} - '@imtbl/passport@2.20.1': - resolution: {integrity: sha512-qqB9KooeqL69jGptTvtjRsiLJf1QielP7GXOs+vDL7fPAP3y8hyz1QNwYVnc0UFxLKNd3yy1OnJqkpgkL7Q77w==} + '@imtbl/passport@2.22.0': + resolution: {integrity: sha512-VDrUiEyL7QZBqIxZ+wBSUjg5gvfVkIrEoYHpMOIBwWXQLtSpxforJW5VR2TvmRil7MrYx13YCRjYIVvWQyYgrg==} engines: {node: '>=20.11.0'} '@imtbl/react-analytics@0.3.4-alpha': resolution: {integrity: sha512-4VWvfm8RZtpLub7+x2D2wNQ507nIVBCSAPA7B5lxdb0cKrHEujM6Y/HScMImHZHvgjUFQT1jiD9b2BL/DS43Pg==} - '@imtbl/sdk@2.20.1': - resolution: {integrity: sha512-5nQRlxh14lKb+LVOoiv2mDzaAcsofCKl3UaiS2UjS2p0Rek/C2daDPRiUjSCr8pNIhpFL71t8Q71kkIBQaVB4Q==} + '@imtbl/sdk@2.22.0': + resolution: {integrity: sha512-CJLxk5EEBI5EuUK+me1fmnUQeT/TuzbPXrSFOyG6JWqISnwsehgbhQH33MHSkw2njrhJ3O04cP/j5vZMRk2+dg==} engines: {node: '>=20.0.0'} peerDependencies: next: ^14.2.0 || ^15.0.0 @@ -4525,15 +4505,15 @@ packages: react: optional: true - '@imtbl/toolkit@2.20.1': - resolution: {integrity: sha512-LtTy4CpX6hSSSDNOFWTG6Ny78VWx52HhKMTQRo19ETCFcW0lEH5UTEfAjjrXVGakQgbk8+fuapkFxFG+rMxJVg==} + '@imtbl/toolkit@2.22.0': + resolution: {integrity: sha512-9tkQucobN3rI5Ff+mFCgCOWwcVy7YTsZI8HZ1W5DRNgCXo4zf7u0uqobkrYsFzoF87seqy+w4l7s9v96iUmrWw==} engines: {node: '>=20.11.0'} - '@imtbl/wallet@2.20.1': - resolution: {integrity: sha512-Qh95lsj+xBvTQSXken4PQp05WECW+2E7OiqJalxZbIzGtewAIXb/tWCrcww8JUEclctD32tZueG5sTjM2uWL6Q==} + '@imtbl/wallet@2.22.0': + resolution: {integrity: sha512-kUHJ+w5yApAbBZ88y5k8wVIZ8im68OR77lNhJ4DblGcp7a75o5k32kkqiklU+CCmSEDDRMW77cbH1Vqlug6fww==} - '@imtbl/webhook@2.20.1': - resolution: {integrity: sha512-uGeyiSsZX+rLeSK3rk2lGE2CvJdw/1wKP0vsfGRd0KugFnxv8gBPGoY6+a+22vqO/7T4uaIMhNMMzUlVNVIblw==} + '@imtbl/webhook@2.22.0': + resolution: {integrity: sha512-YuPyl4xEhGShdNQJgartOFd4s4ZY5rxn/T21nD5jgi4IFN2MC8OFj5JJi1g6T6/4N3adarxHMQY8lDoXjzZJlQ==} '@ioredis/commands@1.2.0': resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} @@ -4876,6 +4856,7 @@ packages: '@metamask/sdk-communication-layer@0.26.4': resolution: {integrity: sha512-+X4GEc5mV1gWK4moSswVlKsUh+RsA48qPlkxBLTUxQODSnyBe0TRMxE6mH+bSrfponnTzvBkGUXyEjvDwDjDHw==} + deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect peerDependencies: cross-fetch: ^4.0.0 eciesjs: ^0.3.16 @@ -4885,6 +4866,7 @@ packages: '@metamask/sdk-install-modal-web@0.26.5': resolution: {integrity: sha512-qVA9Nk+NorGx5hXyODy5wskptE8R7RNYTYt49VbQpJogqbbVe1dnJ98+KaA43PBN4XYMCXmcIhULNiEHGsLynA==} + deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect peerDependencies: i18next: 23.11.5 react: ^18.2.0 @@ -4900,6 +4882,7 @@ packages: '@metamask/sdk@0.26.5': resolution: {integrity: sha512-HS/MPQCCYRS+m3dDdGLcAagwYHiPv9iUshDMBjINUywCtfUN4P2BH8xdvPOgtnzRIuRSMXqMWBbZnTvEvBeQvA==} + deprecated: No longer maintained, superseded by https://docs.metamask.io/metamask-connect peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 @@ -6317,6 +6300,7 @@ packages: '@safe-global/safe-gateway-typescript-sdk@3.22.1': resolution: {integrity: sha512-YApSpx+3h6uejrJVh8PSqXRRAwmsWz8PZERObMGJNC9NPoMhZG/Rvqb2UWmVLrjFh880rqutsB+GrTmJP351PA==} engines: {node: '>=16'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. '@scure/base@1.1.7': resolution: {integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==} @@ -7809,6 +7793,7 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@uniswap/lib@4.0.1-alpha': resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==} @@ -8012,6 +7997,7 @@ packages: '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -16088,7 +16074,7 @@ packages: tar@6.1.15: resolution: {integrity: sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==} engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} @@ -16785,10 +16771,12 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -20309,10 +20297,10 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@imtbl/auth-next-client@2.20.1(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@imtbl/auth-next-client@2.22.0(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: - '@imtbl/auth': 2.20.1 - '@imtbl/auth-next-server': 2.20.1(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@imtbl/auth': 2.22.0 + '@imtbl/auth-next-server': 2.22.0(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) optionalDependencies: next: 14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: 5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -20320,31 +20308,31 @@ snapshots: transitivePeerDependencies: - debug - '@imtbl/auth-next-server@2.20.1(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@imtbl/auth-next-server@2.22.0(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': optionalDependencies: next: 14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: 5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - '@imtbl/auth@2.20.1': + '@imtbl/auth@2.22.0': dependencies: - '@imtbl/generated-clients': 2.20.1 - '@imtbl/metrics': 2.20.1 + '@imtbl/generated-clients': 2.22.0 + '@imtbl/metrics': 2.22.0 localforage: 1.10.0 oidc-client-ts: 3.4.1 transitivePeerDependencies: - debug - '@imtbl/blockchain-data@2.20.1': + '@imtbl/blockchain-data@2.22.0': dependencies: - '@imtbl/config': 2.20.1 - '@imtbl/generated-clients': 2.20.1 + '@imtbl/config': 2.22.0 + '@imtbl/generated-clients': 2.22.0 axios: 1.7.7 transitivePeerDependencies: - debug - '@imtbl/bridge-sdk@2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/bridge-sdk@2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.20.1 + '@imtbl/config': 2.22.0 '@jest/globals': 29.7.0 axios: 1.7.7 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20354,16 +20342,16 @@ snapshots: - supports-color - utf-8-validate - '@imtbl/checkout-sdk@2.20.1(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(typescript@5.6.2)(utf-8-validate@5.0.10)': + '@imtbl/checkout-sdk@2.22.0(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(typescript@5.6.2)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/blockchain-data': 2.20.1 - '@imtbl/bridge-sdk': 2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/config': 2.20.1 - '@imtbl/dex-sdk': 2.20.1(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) - '@imtbl/generated-clients': 2.20.1 - '@imtbl/metrics': 2.20.1 - '@imtbl/orderbook': 2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/passport': 2.20.1(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@imtbl/blockchain-data': 2.22.0 + '@imtbl/bridge-sdk': 2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/config': 2.22.0 + '@imtbl/dex-sdk': 2.22.0(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10) + '@imtbl/generated-clients': 2.22.0 + '@imtbl/metrics': 2.22.0 + '@imtbl/orderbook': 2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/passport': 2.22.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) '@metamask/detect-provider': 2.0.0 axios: 1.7.7 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20378,9 +20366,9 @@ snapshots: - utf-8-validate - zod - '@imtbl/config@2.20.1': + '@imtbl/config@2.22.0': dependencies: - '@imtbl/metrics': 2.20.1 + '@imtbl/metrics': 2.22.0 '@imtbl/contracts@2.2.6(bufferutil@4.0.8)(eslint@9.16.0(jiti@1.21.0))(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)': dependencies: @@ -20407,9 +20395,9 @@ snapshots: dependencies: '@openzeppelin/contracts': 5.0.2 - '@imtbl/dex-sdk@2.20.1(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': + '@imtbl/dex-sdk@2.22.0(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.20.1 + '@imtbl/config': 2.22.0 '@uniswap/sdk-core': 3.2.3 '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) '@uniswap/v3-sdk': 3.10.0(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10)) @@ -20419,7 +20407,7 @@ snapshots: - hardhat - utf-8-validate - '@imtbl/generated-clients@2.20.1': + '@imtbl/generated-clients@2.22.0': dependencies: axios: 1.7.7 transitivePeerDependencies: @@ -20429,18 +20417,18 @@ snapshots: dependencies: buffer: 6.0.3 - '@imtbl/metrics@2.20.1': + '@imtbl/metrics@2.22.0': dependencies: global-const: 0.1.2 lru-memorise: 0.3.0 - '@imtbl/minting-backend@2.20.1': + '@imtbl/minting-backend@2.22.0': dependencies: - '@imtbl/blockchain-data': 2.20.1 - '@imtbl/config': 2.20.1 - '@imtbl/generated-clients': 2.20.1 - '@imtbl/metrics': 2.20.1 - '@imtbl/webhook': 2.20.1 + '@imtbl/blockchain-data': 2.22.0 + '@imtbl/config': 2.22.0 + '@imtbl/generated-clients': 2.22.0 + '@imtbl/metrics': 2.22.0 + '@imtbl/webhook': 2.22.0 uuid: 9.0.1 optionalDependencies: pg: 8.11.5 @@ -20449,10 +20437,10 @@ snapshots: - debug - pg-native - '@imtbl/orderbook@2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/orderbook@2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/config': 2.20.1 - '@imtbl/metrics': 2.20.1 + '@imtbl/config': 2.22.0 + '@imtbl/metrics': 2.22.0 '@opensea/seaport-js': 4.0.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) axios: 1.7.7 ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -20463,14 +20451,14 @@ snapshots: - debug - utf-8-validate - '@imtbl/passport@2.20.1(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': + '@imtbl/passport@2.22.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/auth': 2.20.1 - '@imtbl/config': 2.20.1 - '@imtbl/generated-clients': 2.20.1 - '@imtbl/metrics': 2.20.1 - '@imtbl/toolkit': 2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/wallet': 2.20.1(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@imtbl/auth': 2.22.0 + '@imtbl/config': 2.22.0 + '@imtbl/generated-clients': 2.22.0 + '@imtbl/metrics': 2.22.0 + '@imtbl/toolkit': 2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/wallet': 2.22.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) ethers: 6.13.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) localforage: 1.10.0 oidc-client-ts: 3.4.1 @@ -20489,19 +20477,19 @@ snapshots: - encoding - supports-color - '@imtbl/sdk@2.20.1(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10)': + '@imtbl/sdk@2.22.0(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(typescript@5.6.2)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/auth': 2.20.1 - '@imtbl/auth-next-client': 2.20.1(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) - '@imtbl/auth-next-server': 2.20.1(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@imtbl/blockchain-data': 2.20.1 - '@imtbl/checkout-sdk': 2.20.1(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(typescript@5.6.2)(utf-8-validate@5.0.10) - '@imtbl/config': 2.20.1 - '@imtbl/minting-backend': 2.20.1 - '@imtbl/orderbook': 2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - '@imtbl/passport': 2.20.1(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) - '@imtbl/wallet': 2.20.1(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) - '@imtbl/webhook': 2.20.1 + '@imtbl/auth': 2.22.0 + '@imtbl/auth-next-client': 2.22.0(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@imtbl/auth-next-server': 2.22.0(next-auth@5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1))(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@imtbl/blockchain-data': 2.22.0 + '@imtbl/checkout-sdk': 2.22.0(bufferutil@4.0.8)(hardhat@2.22.6(bufferutil@4.0.8)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))(typescript@5.6.2)(utf-8-validate@5.0.10))(typescript@5.6.2)(utf-8-validate@5.0.10) + '@imtbl/config': 2.22.0 + '@imtbl/minting-backend': 2.22.0 + '@imtbl/orderbook': 2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@imtbl/passport': 2.22.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@imtbl/wallet': 2.22.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) + '@imtbl/webhook': 2.22.0 optionalDependencies: next: 14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: 5.0.0-beta.30(next@14.2.25(@babel/core@7.26.10)(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -20516,7 +20504,7 @@ snapshots: - utf-8-validate - zod - '@imtbl/toolkit@2.20.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@imtbl/toolkit@2.22.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: '@metamask/detect-provider': 2.0.0 axios: 1.7.7 @@ -20529,11 +20517,11 @@ snapshots: - debug - utf-8-validate - '@imtbl/wallet@2.20.1(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': + '@imtbl/wallet@2.22.0(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10)': dependencies: - '@imtbl/auth': 2.20.1 - '@imtbl/generated-clients': 2.20.1 - '@imtbl/metrics': 2.20.1 + '@imtbl/auth': 2.22.0 + '@imtbl/generated-clients': 2.22.0 + '@imtbl/metrics': 2.22.0 viem: 2.18.2(bufferutil@4.0.8)(typescript@5.6.2)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -20542,10 +20530,10 @@ snapshots: - utf-8-validate - zod - '@imtbl/webhook@2.20.1': + '@imtbl/webhook@2.22.0': dependencies: - '@imtbl/config': 2.20.1 - '@imtbl/generated-clients': 2.20.1 + '@imtbl/config': 2.22.0 + '@imtbl/generated-clients': 2.22.0 sns-validator: 0.3.5 transitivePeerDependencies: - debug @@ -20685,6 +20673,43 @@ snapshots: - ts-node - utf-8-validate + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0(node-notifier@8.0.2) + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.14.13 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.14.13)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + optionalDependencies: + node-notifier: 8.0.2 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@20.14.13)(typescript@5.6.2))': dependencies: '@jest/console': 29.7.0 @@ -22111,7 +22136,7 @@ snapshots: json5: 2.2.3 msgpackr: 1.11.5 nullthrows: 1.1.1 - semver: 7.7.1 + semver: 7.7.3 transitivePeerDependencies: - '@swc/helpers' - napi-wasm @@ -22241,7 +22266,7 @@ snapshots: '@parcel/utils': 2.16.3 '@parcel/workers': 2.16.3(@parcel/core@2.16.3(@swc/helpers@0.5.15)) '@swc/core': 1.15.3(@swc/helpers@0.5.15) - semver: 7.7.1 + semver: 7.7.3 transitivePeerDependencies: - '@swc/helpers' - napi-wasm @@ -26151,20 +26176,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-jest@29.7.0(@babel/core@7.26.9): - dependencies: - '@babel/core': 7.26.9 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.26.9) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - optional: true - babel-loader@8.3.0(@babel/core@7.26.9)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)): dependencies: '@babel/core': 7.26.9 @@ -26357,13 +26368,6 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) - babel-preset-jest@29.6.3(@babel/core@7.26.9): - dependencies: - '@babel/core': 7.26.9 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.9) - optional: true - babel-preset-react-app@10.0.1: dependencies: '@babel/core': 7.26.10 @@ -27309,6 +27313,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)): dependencies: '@jest/types': 29.6.3 @@ -28258,7 +28277,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 @@ -28269,13 +28288,13 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint-plugin-jsx-a11y@6.9.0(eslint@8.57.0))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.0))(eslint-plugin-react@7.35.0(eslint@8.57.0))(eslint@8.57.0): dependencies: eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.0) @@ -28289,7 +28308,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) @@ -28326,7 +28345,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) @@ -28344,7 +28363,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) @@ -28355,23 +28374,23 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): dependencies: '@babel/core': 7.26.10 - '@babel/eslint-parser': 7.22.9(@babel/core@7.26.10)(eslint@9.16.0(jiti@1.21.0)) + '@babel/eslint-parser': 7.22.9(@babel/core@7.26.10)(eslint@8.57.0) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/parser': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) babel-preset-react-app: 10.0.1 confusing-browser-globals: 1.0.11 - eslint: 9.16.0(jiti@1.21.0) - eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-plugin-jsx-a11y: 6.9.0(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-react: 7.35.0(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-react-hooks: 4.6.0(eslint@9.16.0(jiti@1.21.0)) - eslint-plugin-testing-library: 5.11.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) + eslint: 8.57.0 + eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) + eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) + eslint-plugin-react: 7.35.0(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) + eslint-plugin-testing-library: 5.11.0(eslint@8.57.0)(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -28382,23 +28401,23 @@ snapshots: - jest - supports-color - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): dependencies: '@babel/core': 7.26.10 - '@babel/eslint-parser': 7.22.9(@babel/core@7.26.10)(eslint@8.57.0) + '@babel/eslint-parser': 7.22.9(@babel/core@7.26.10)(eslint@9.16.0(jiti@1.21.0)) '@rushstack/eslint-patch': 1.10.4 - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/parser': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) babel-preset-react-app: 10.0.1 confusing-browser-globals: 1.0.11 - eslint: 8.57.0 - eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0) - eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) - eslint-plugin-react: 7.35.0(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) - eslint-plugin-testing-library: 5.11.0(eslint@8.57.0)(typescript@5.6.2) + eslint: 9.16.0(jiti@1.21.0) + eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0)) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)) + eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) + eslint-plugin-jsx-a11y: 6.9.0(eslint@9.16.0(jiti@1.21.0)) + eslint-plugin-react: 7.35.0(eslint@9.16.0(jiti@1.21.0)) + eslint-plugin-react-hooks: 4.6.0(eslint@9.16.0(jiti@1.21.0)) + eslint-plugin-testing-library: 5.11.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -28435,6 +28454,24 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + dependencies: + debug: 4.3.7(supports-color@8.1.1) + enhanced-resolve: 5.15.0 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + get-tsconfig: 4.6.2 + globby: 13.2.2 + is-core-module: 2.15.0 + is-glob: 4.0.3 + synckit: 0.8.5 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 @@ -28446,6 +28483,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.16.0(jiti@1.21.0)): dependencies: debug: 3.2.7 @@ -28456,19 +28504,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0)): + eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0): dependencies: '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.10) '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) - eslint: 9.16.0(jiti@1.21.0) + eslint: 8.57.0 lodash: 4.17.21 string-natural-compare: 3.0.1 - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0): + eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0)): dependencies: '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.9) '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.9) - eslint: 8.57.0 + eslint: 9.16.0(jiti@1.21.0) lodash: 4.17.21 string-natural-compare: 3.0.1 @@ -28499,33 +28547,6 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0): - dependencies: - array-includes: 3.1.8 - array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.15.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.0 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.57.0)(typescript@5.6.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0)): dependencies: array-includes: 3.1.8 @@ -28564,12 +28585,12 @@ snapshots: - supports-color - typescript - eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): + eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2): dependencies: '@typescript-eslint/experimental-utils': 5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) eslint: 9.16.0(jiti@1.21.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2) jest: 27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10) transitivePeerDependencies: - supports-color @@ -30915,6 +30936,27 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + optionalDependencies: + node-notifier: 8.0.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)): dependencies: '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) @@ -31777,7 +31819,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.1 + semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -31962,6 +32004,20 @@ snapshots: - supports-color - ts-node + jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) + optionalDependencies: + node-notifier: 8.0.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)): dependencies: '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) @@ -34997,7 +35053,7 @@ snapshots: '@remix-run/router': 1.7.2 react: 18.3.1 - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): dependencies: '@babel/core': 7.26.9 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)))(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) @@ -35014,9 +35070,9 @@ snapshots: css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.1)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) dotenv: 10.0.0 dotenv-expand: 5.1.0 - eslint: 9.16.0(jiti@1.21.0) - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-webpack-plugin: 3.2.0(eslint@9.16.0(jiti@1.21.0))(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) + eslint: 8.57.0 + eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.10))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) + eslint-webpack-plugin: 3.2.0(eslint@8.57.0)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) file-loader: 6.2.0(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) fs-extra: 10.1.0 html-webpack-plugin: 5.5.3(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) @@ -35033,7 +35089,7 @@ snapshots: prompts: 2.4.2 react: 18.3.1 react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) + react-dev-utils: 12.0.1(eslint@8.57.0)(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) react-refresh: 0.11.0 resolve: 1.22.8 resolve-url-loader: 4.0.0 @@ -35083,7 +35139,7 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@8.57.0)(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): + react-scripts@5.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/babel__core@7.20.5)(bufferutil@4.0.8)(esbuild@0.23.1)(eslint@9.16.0(jiti@1.21.0))(node-notifier@8.0.2)(react@18.3.1)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(type-fest@2.19.0)(typescript@5.6.2)(utf-8-validate@5.0.10): dependencies: '@babel/core': 7.26.9 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.11.0)(type-fest@2.19.0)(webpack-dev-server@4.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)))(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) @@ -35100,9 +35156,9 @@ snapshots: css-minimizer-webpack-plugin: 3.4.1(esbuild@0.23.1)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) dotenv: 10.0.0 dotenv-expand: 5.1.0 - eslint: 8.57.0 - eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@8.57.0)(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) - eslint-webpack-plugin: 3.2.0(eslint@8.57.0)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) + eslint: 9.16.0(jiti@1.21.0) + eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9))(eslint@9.16.0(jiti@1.21.0))(jest@27.5.1(bufferutil@4.0.8)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2))(utf-8-validate@5.0.10))(typescript@5.6.2) + eslint-webpack-plugin: 3.2.0(eslint@9.16.0(jiti@1.21.0))(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) file-loader: 6.2.0(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) fs-extra: 10.1.0 html-webpack-plugin: 5.5.3(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) @@ -35119,7 +35175,7 @@ snapshots: prompts: 2.4.2 react: 18.3.1 react-app-polyfill: 3.0.0 - react-dev-utils: 12.0.1(eslint@8.57.0)(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) + react-dev-utils: 12.0.1(eslint@9.16.0(jiti@1.21.0))(typescript@5.6.2)(webpack@5.88.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.23.1)) react-refresh: 0.11.0 resolve: 1.22.8 resolve-url-loader: 4.0.0 @@ -36957,12 +37013,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.23.1 - ts-jest@29.2.5(@babel/core@7.26.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.9))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(esbuild@0.23.1)(jest@29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/node@22.19.7)(typescript@5.6.2)) + jest: 29.7.0(@types/node@22.19.7)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -36971,10 +37027,10 @@ snapshots: typescript: 5.6.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.9 + '@babel/core': 7.26.10 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.9) + babel-jest: 29.7.0(@babel/core@7.26.10) esbuild: 0.23.1 ts-mockito@2.6.1: