mirror of
https://github.com/selfxyz/self.git
synced 2026-01-10 07:08:10 -05:00
move fcm token storage (#1175)
* move fcm token from proving store to setting store for better separation of concerns.
* use analytics on adapter
* Revert "use analytics on adapter"
This reverts commit 4854d6fa87.
* prettier
* please be good
* ik heb oopsie wooopsie
* remove
This commit is contained in:
@@ -20,6 +20,7 @@ import { navigationRef } from '@/navigation';
|
||||
import { unsafe_getPrivateKey } from '@/providers/authProvider';
|
||||
import { selfClientDocumentsAdapter } from '@/providers/passportDataProvider';
|
||||
import { logNFCEvent, logProofEvent } from '@/Sentry';
|
||||
import { useSettingStore } from '@/stores/settingStore';
|
||||
import analytics from '@/utils/analytics';
|
||||
|
||||
type GlobalCrypto = { crypto?: { subtle?: Crypto['subtle'] } };
|
||||
@@ -95,15 +96,6 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
auth: {
|
||||
getPrivateKey: () => unsafe_getPrivateKey(),
|
||||
},
|
||||
notification: {
|
||||
registerDeviceToken: async (sessionId, deviceToken, isMock) => {
|
||||
// Forward to our app-level function which handles staging vs production
|
||||
// and also fetches the token if not provided
|
||||
const { registerDeviceToken: registerFirebaseDeviceToken } =
|
||||
await import('@/utils/notifications/notificationService');
|
||||
return registerFirebaseDeviceToken(sessionId, deviceToken, isMock);
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -158,6 +150,35 @@ export const SelfClientProvider = ({ children }: PropsWithChildren) => {
|
||||
}
|
||||
});
|
||||
|
||||
addListener(
|
||||
SdkEvents.PROVING_BEGIN_GENERATION,
|
||||
async ({ uuid, isMock, context }) => {
|
||||
const { fcmToken } = useSettingStore.getState();
|
||||
|
||||
if (fcmToken) {
|
||||
try {
|
||||
analytics().trackEvent('DEVICE_TOKEN_REG_STARTED');
|
||||
logProofEvent('info', 'Device token registration started', context);
|
||||
|
||||
const { registerDeviceToken: registerFirebaseDeviceToken } =
|
||||
await import('@/utils/notifications/notificationService');
|
||||
await registerFirebaseDeviceToken(uuid, fcmToken, isMock);
|
||||
|
||||
analytics().trackEvent('DEVICE_TOKEN_REG_SUCCESS');
|
||||
logProofEvent('info', 'Device token registration success', context);
|
||||
} catch (error) {
|
||||
logProofEvent('warn', 'Device token registration failed', context, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
console.error('Error registering device token:', error);
|
||||
analytics().trackEvent('DEVICE_TOKEN_REG_FAILED', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
addListener(SdkEvents.PROOF_EVENT, ({ level, context, event, details }) => {
|
||||
// Log proof events for monitoring/debugging
|
||||
logProofEvent(level, event, context, details);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Title } from '@/components/typography/Title';
|
||||
import useHapticNavigation from '@/hooks/useHapticNavigation';
|
||||
import { ExpandableBottomLayout } from '@/layouts/ExpandableBottomLayout';
|
||||
import { styles } from '@/screens/prove/ProofRequestStatusScreen';
|
||||
import { useSettingStore } from '@/stores/settingStore';
|
||||
import { flushAllAnalytics, trackNfcEvent } from '@/utils/analytics';
|
||||
import { black, white } from '@/utils/colors';
|
||||
import { notificationSuccess } from '@/utils/haptic';
|
||||
@@ -40,8 +41,8 @@ const ConfirmBelongingScreen: React.FC<ConfirmBelongingScreenProps> = () => {
|
||||
const [_requestingPermission, setRequestingPermission] = useState(false);
|
||||
const currentState = useProvingStore(state => state.currentState);
|
||||
const init = useProvingStore(state => state.init);
|
||||
const setFcmToken = useProvingStore(state => state.setFcmToken);
|
||||
const setUserConfirmed = useProvingStore(state => state.setUserConfirmed);
|
||||
const setFcmToken = useSettingStore(state => state.setFcmToken);
|
||||
const isReadyToProve = currentState === 'ready_to_prove';
|
||||
useEffect(() => {
|
||||
notificationSuccess();
|
||||
@@ -74,7 +75,7 @@ const ConfirmBelongingScreen: React.FC<ConfirmBelongingScreenProps> = () => {
|
||||
if (permissionGranted) {
|
||||
const token = await getFCMToken();
|
||||
if (token) {
|
||||
setFcmToken(token, selfClient);
|
||||
setFcmToken(token);
|
||||
trackEvent(ProofEvents.FCM_TOKEN_STORED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ interface PersistedSettingsState {
|
||||
isDevMode: boolean;
|
||||
setDevModeOn: () => void;
|
||||
setDevModeOff: () => void;
|
||||
fcmToken: string | null;
|
||||
setFcmToken: (token: string | null) => void;
|
||||
}
|
||||
|
||||
interface NonPersistedSettingsState {
|
||||
@@ -69,6 +71,9 @@ export const useSettingStore = create<SettingsState>()(
|
||||
setDevModeOn: () => set({ isDevMode: true }),
|
||||
setDevModeOff: () => set({ isDevMode: false }),
|
||||
|
||||
fcmToken: null,
|
||||
setFcmToken: (token: string | null) => set({ fcmToken: token }),
|
||||
|
||||
// Non-persisted state (will not be saved to storage)
|
||||
hideNetworkModal: false,
|
||||
setHideNetworkModal: (hideNetworkModal: boolean) => {
|
||||
|
||||
@@ -76,9 +76,6 @@ function createTestClient() {
|
||||
})),
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
registerDeviceToken: async () => Promise.resolve(),
|
||||
},
|
||||
crypto: {
|
||||
hash: jest.fn(),
|
||||
sign: jest.fn(),
|
||||
|
||||
@@ -62,7 +62,4 @@ export const mockAdapters = {
|
||||
network: mockNetwork,
|
||||
crypto: mockCrypto,
|
||||
documents: mockDocuments,
|
||||
notification: {
|
||||
registerDeviceToken: async () => Promise.resolve(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ const optionalDefaults: Required<Pick<Adapters, 'clock' | 'logger'>> = {
|
||||
},
|
||||
};
|
||||
|
||||
const REQUIRED_ADAPTERS = ['auth', 'scanner', 'network', 'crypto', 'documents', 'notification'] as const;
|
||||
const REQUIRED_ADAPTERS = ['auth', 'scanner', 'network', 'crypto', 'documents'] as const;
|
||||
|
||||
export const createListenersMap = (): {
|
||||
map: Map<SDKEvent, Set<(p: any) => void>>;
|
||||
@@ -132,13 +132,6 @@ export function createSelfClient({
|
||||
return adapters.auth.getPrivateKey();
|
||||
}
|
||||
|
||||
async function registerNotificationsToken(sessionId: string, deviceToken?: string, isMock?: boolean): Promise<void> {
|
||||
if (!_adapters.notification) {
|
||||
throw notImplemented('notification');
|
||||
}
|
||||
return _adapters.notification.registerDeviceToken(sessionId, deviceToken, isMock);
|
||||
}
|
||||
|
||||
async function hasPrivateKey(): Promise<boolean> {
|
||||
if (!adapters.auth) return false;
|
||||
try {
|
||||
@@ -160,7 +153,6 @@ export function createSelfClient({
|
||||
logProofEvent: (level: LogLevel, message: string, context: ProofContext, details?: Record<string, any>) => {
|
||||
emit(SdkEvents.PROOF_EVENT, { context, event: message, details, level });
|
||||
},
|
||||
registerNotificationsToken,
|
||||
// TODO: inline for now
|
||||
loadDocumentCatalog: async () => {
|
||||
return _adapters.documents.loadDocumentCatalog();
|
||||
|
||||
@@ -214,9 +214,7 @@ export interface ProvingState {
|
||||
error_code: string | null;
|
||||
reason: string | null;
|
||||
endpointType: EndpointType | null;
|
||||
fcmToken: string | null;
|
||||
env: 'prod' | 'stg' | null;
|
||||
setFcmToken: (token: string, selfClient: SelfClient) => void;
|
||||
init: (
|
||||
selfClient: SelfClient,
|
||||
circuitType: 'dsc' | 'disclose' | 'register',
|
||||
@@ -487,11 +485,6 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
error_code: null,
|
||||
reason: null,
|
||||
endpointType: null,
|
||||
fcmToken: null,
|
||||
setFcmToken: (token: string, selfClient: SelfClient) => {
|
||||
set({ fcmToken: token });
|
||||
selfClient.trackEvent(ProofEvents.FCM_TOKEN_STORED);
|
||||
},
|
||||
_handleWebSocketMessage: async (event: MessageEvent, selfClient: SelfClient) => {
|
||||
if (!actor) {
|
||||
console.error('Cannot process message: State machine not initialized.');
|
||||
@@ -1201,7 +1194,7 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
startProving: async (selfClient: SelfClient) => {
|
||||
_checkActorInitialized(actor);
|
||||
const startTime = Date.now();
|
||||
const { wsConnection, sharedKey, passportData, secret, uuid, fcmToken } = get();
|
||||
const { wsConnection, sharedKey, passportData, secret, uuid } = get();
|
||||
const context = createProofContext(selfClient, 'startProving', {
|
||||
sessionId: uuid || get().uuid || 'unknown-session',
|
||||
});
|
||||
@@ -1223,24 +1216,12 @@ export const useProvingStore = create<ProvingState>((set, get) => {
|
||||
}
|
||||
|
||||
try {
|
||||
if (fcmToken) {
|
||||
try {
|
||||
const isMockPassport = passportData?.mock;
|
||||
selfClient.trackEvent(ProofEvents.DEVICE_TOKEN_REG_STARTED);
|
||||
selfClient.logProofEvent('info', 'Device token registration started', context);
|
||||
await selfClient.registerNotificationsToken(uuid, fcmToken, isMockPassport);
|
||||
selfClient.trackEvent(ProofEvents.DEVICE_TOKEN_REG_SUCCESS);
|
||||
selfClient.logProofEvent('info', 'Device token registration success', context);
|
||||
} catch (error) {
|
||||
selfClient.logProofEvent('warn', 'Device token registration failed', context, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
console.error('Error registering device token:', error);
|
||||
selfClient.trackEvent(ProofEvents.DEVICE_TOKEN_REG_FAILED, {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
// Emit event for FCM token registration
|
||||
selfClient.emit(SdkEvents.PROVING_BEGIN_GENERATION, {
|
||||
uuid,
|
||||
isMock: passportData?.mock ?? false,
|
||||
context,
|
||||
});
|
||||
|
||||
selfClient.trackEvent(ProofEvents.PAYLOAD_GEN_STARTED);
|
||||
selfClient.logProofEvent('info', 'Payload generation started', context);
|
||||
|
||||
@@ -68,6 +68,14 @@ export enum SdkEvents {
|
||||
* and guide them through the recovery process to regain access.
|
||||
*/
|
||||
PROVING_ACCOUNT_RECOVERY_REQUIRED = 'PROVING_ACCOUNT_RECOVERY_REQUIRED',
|
||||
|
||||
/**
|
||||
* Emitted when the proving generation process begins.
|
||||
*
|
||||
* **Recommended:** Use this to handle notification token registration and other setup tasks
|
||||
* that need to occur when proof generation starts.
|
||||
*/
|
||||
PROVING_BEGIN_GENERATION = 'PROVING_BEGIN_GENERATION',
|
||||
/**
|
||||
* Emitted for various proof-related events during the proving process.
|
||||
*
|
||||
@@ -97,6 +105,11 @@ export interface SDKEventMap {
|
||||
documentCategory: DocumentCategory | null;
|
||||
};
|
||||
[SdkEvents.PROVING_ACCOUNT_RECOVERY_REQUIRED]: undefined;
|
||||
[SdkEvents.PROVING_BEGIN_GENERATION]: {
|
||||
uuid: string;
|
||||
isMock: boolean;
|
||||
context: ProofContext;
|
||||
};
|
||||
|
||||
[SdkEvents.PROGRESS]: Progress;
|
||||
[SdkEvents.ERROR]: Error;
|
||||
|
||||
@@ -90,10 +90,6 @@ export interface MRZValidation {
|
||||
overall: boolean;
|
||||
}
|
||||
|
||||
export interface NotificationAdapter {
|
||||
registerDeviceToken(sessionId: string, deviceToken?: string, isMock?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export type LogLevel = 'info' | 'warn' | 'error';
|
||||
|
||||
export interface Progress {
|
||||
@@ -110,7 +106,6 @@ export interface Adapters {
|
||||
analytics?: AnalyticsAdapter;
|
||||
auth: AuthAdapter;
|
||||
documents: DocumentsAdapter;
|
||||
notification: NotificationAdapter;
|
||||
}
|
||||
|
||||
export interface LoggerAdapter {
|
||||
@@ -181,7 +176,6 @@ export interface SelfClient {
|
||||
logProofEvent(level: LogLevel, message: string, context: ProofContext, details?: Record<string, any>): void;
|
||||
loadDocumentCatalog(): Promise<DocumentCatalog>;
|
||||
saveDocumentCatalog(catalog: DocumentCatalog): Promise<void>;
|
||||
registerNotificationsToken(sessionId: string, deviceToken?: string, isMock?: boolean): Promise<void>;
|
||||
loadDocumentById(id: string): Promise<IDDocument | null>;
|
||||
saveDocument(id: string, passportData: IDDocument): Promise<void>;
|
||||
deleteDocument(id: string): Promise<void>;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { CryptoAdapter, DocumentsAdapter, NetworkAdapter, ScannerAdapter } from '../src';
|
||||
import { createListenersMap, createSelfClient, SdkEvents } from '../src/index';
|
||||
import { AuthAdapter, NotificationAdapter } from '../src/types/public';
|
||||
import { AuthAdapter } from '../src/types/public';
|
||||
|
||||
describe('createSelfClient', () => {
|
||||
// Test eager validation during client creation
|
||||
@@ -20,7 +20,6 @@ describe('createSelfClient', () => {
|
||||
auth,
|
||||
network,
|
||||
crypto,
|
||||
notification,
|
||||
},
|
||||
}),
|
||||
).toThrow('scanner adapter not provided');
|
||||
@@ -50,7 +49,7 @@ describe('createSelfClient', () => {
|
||||
it('creates client successfully with all required adapters', () => {
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { scanner, network, crypto, documents, auth, notification },
|
||||
adapters: { scanner, network, crypto, documents, auth },
|
||||
listeners: new Map(),
|
||||
});
|
||||
expect(client).toBeTruthy();
|
||||
@@ -60,7 +59,7 @@ describe('createSelfClient', () => {
|
||||
const scanMock = vi.fn().mockResolvedValue({ mode: 'qr', data: 'self://ok' });
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth, notification },
|
||||
adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth },
|
||||
listeners: new Map(),
|
||||
});
|
||||
const result = await client.scanDocument({ mode: 'qr' });
|
||||
@@ -78,7 +77,7 @@ describe('createSelfClient', () => {
|
||||
const scanMock = vi.fn().mockRejectedValue(err);
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth, notification },
|
||||
adapters: { scanner: { scan: scanMock }, network, crypto, documents, auth },
|
||||
listeners: new Map(),
|
||||
});
|
||||
await expect(client.scanDocument({ mode: 'qr' })).rejects.toBe(err);
|
||||
@@ -97,7 +96,7 @@ describe('createSelfClient', () => {
|
||||
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { scanner, network, crypto, documents, auth, notification },
|
||||
adapters: { scanner, network, crypto, documents, auth },
|
||||
listeners: listeners.map,
|
||||
});
|
||||
|
||||
@@ -125,7 +124,7 @@ describe('createSelfClient', () => {
|
||||
it('parses MRZ via client', () => {
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { scanner, network, crypto, documents, auth, notification },
|
||||
adapters: { scanner, network, crypto, documents, auth },
|
||||
listeners: new Map(),
|
||||
});
|
||||
const sample = `P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10`;
|
||||
@@ -134,33 +133,12 @@ describe('createSelfClient', () => {
|
||||
expect(info.validation?.overall).toBe(true);
|
||||
});
|
||||
|
||||
it('registers device token when notification adapter is given', async () => {
|
||||
const registerDeviceToken = vi.fn(() => Promise.resolve());
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: {
|
||||
notification: { registerDeviceToken },
|
||||
scanner,
|
||||
network,
|
||||
crypto,
|
||||
documents,
|
||||
auth: { getPrivateKey: () => Promise.resolve('stubbed-private-key') },
|
||||
},
|
||||
listeners: new Map(),
|
||||
});
|
||||
|
||||
await client.registerNotificationsToken('session-id', 'test-token', true);
|
||||
expect(registerDeviceToken).toHaveBeenCalledOnce();
|
||||
expect(registerDeviceToken).toHaveBeenCalledWith('session-id', 'test-token', true);
|
||||
});
|
||||
|
||||
describe('when analytics adapter is given', () => {
|
||||
it('calls that adapter for trackEvent', () => {
|
||||
const trackEvent = vi.fn();
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: {
|
||||
notification,
|
||||
scanner,
|
||||
network,
|
||||
crypto,
|
||||
@@ -183,7 +161,7 @@ describe('createSelfClient', () => {
|
||||
const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key'));
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { notification, scanner, network, crypto, documents, auth: { getPrivateKey } },
|
||||
adapters: { scanner, network, crypto, documents, auth: { getPrivateKey } },
|
||||
listeners: new Map(),
|
||||
});
|
||||
|
||||
@@ -193,7 +171,7 @@ describe('createSelfClient', () => {
|
||||
const getPrivateKey = vi.fn(() => Promise.resolve('stubbed-private-key'));
|
||||
const client = createSelfClient({
|
||||
config: {},
|
||||
adapters: { notification, scanner, network, crypto, documents, auth: { getPrivateKey } },
|
||||
adapters: { scanner, network, crypto, documents, auth: { getPrivateKey } },
|
||||
listeners: new Map(),
|
||||
});
|
||||
await expect(client.hasPrivateKey()).resolves.toBe(true);
|
||||
@@ -234,6 +212,3 @@ const documents: DocumentsAdapter = {
|
||||
saveDocument: async () => {},
|
||||
deleteDocument: async () => {},
|
||||
};
|
||||
const notification: NotificationAdapter = {
|
||||
registerDeviceToken: async () => Promise.resolve(),
|
||||
};
|
||||
|
||||
@@ -14,9 +14,6 @@ const createMockSelfClientWithDocumentsAdapter = (documentsAdapter: DocumentsAda
|
||||
config: defaultConfig,
|
||||
listeners: new Map(),
|
||||
adapters: {
|
||||
notification: {
|
||||
registerDeviceToken: async () => Promise.resolve(),
|
||||
},
|
||||
auth: {
|
||||
getPrivateKey: async () => null,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
/* eslint-disable sort-exports/sort-exports */
|
||||
import type { CryptoAdapter, DocumentsAdapter, NetworkAdapter, ScannerAdapter } from '../../src';
|
||||
import type { NotificationAdapter } from '../../src/types/public';
|
||||
|
||||
// Shared test data
|
||||
export const sampleMRZ = `P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10`;
|
||||
@@ -66,17 +65,12 @@ const mockAuth = {
|
||||
getPrivateKey: async () => 'stubbed-private-key',
|
||||
};
|
||||
|
||||
const mockNotification: NotificationAdapter = {
|
||||
registerDeviceToken: async () => Promise.resolve(),
|
||||
};
|
||||
|
||||
export const mockAdapters = {
|
||||
scanner: mockScanner,
|
||||
network: mockNetwork,
|
||||
crypto: mockCrypto,
|
||||
documents: mockDocuments,
|
||||
auth: mockAuth,
|
||||
notification: mockNotification,
|
||||
};
|
||||
|
||||
// Shared test expectations
|
||||
|
||||
@@ -81,11 +81,6 @@ export function SelfClientProvider({ children }: PropsWithChildren) {
|
||||
}
|
||||
},
|
||||
},
|
||||
notification: {
|
||||
async registerDeviceToken(): Promise<void> {
|
||||
// No-op notification adapter for the demo application
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -328,7 +328,7 @@ export class SelfBackendVerifier {
|
||||
? verificationConfig.minimumAge <= Number.parseInt(genericDiscloseOutput.minimumAge, 10)
|
||||
: true,
|
||||
isOfacValid:
|
||||
//isOfacValid is true when a person is in OFAC list
|
||||
//isOfacValid is true when a person is in OFAC list
|
||||
verificationConfig.ofac !== undefined && verificationConfig.ofac ? cumulativeOfac : false,
|
||||
},
|
||||
forbiddenCountriesList,
|
||||
|
||||
Reference in New Issue
Block a user