expo-finance-kit
Version:
Native Expo module for Apple FinanceKit - Access financial data from Apple Card and other accounts
224 lines (198 loc) • 6.14 kB
text/typescript
/**
* Authorization management module for Expo Finance Kit
* Handles FinanceKit authorization flows and status management
*/
import { Platform } from 'react-native';
import ExpoFinanceKit from '../ExpoFinanceKitModule';
import {
AuthorizationStatus,
AuthorizationResult,
AuthorizationStatusChangedPayload,
FinanceKitErrorCode
} from '../ExpoFinanceKit.types';
import { createFinanceKitError } from '../utils/errors';
/**
* Requests authorization to access financial data
* @returns Promise resolving to authorization result
*/
export async function requestAuthorization(): Promise<AuthorizationResult> {
if (Platform.OS === 'android') {
return {
granted: false,
status: 'unavailable',
};
}
try {
const granted = await ExpoFinanceKit.requestAuthorization();
const status = await ExpoFinanceKit.getAuthorizationStatus();
return {
granted,
status: status as AuthorizationStatus,
};
} catch (error) {
console.error('Error requesting FinanceKit authorization:', error);
return {
granted: false,
status: 'denied',
};
}
}
/**
* Gets current authorization status
* @returns Promise resolving to current authorization status
*/
export async function getAuthorizationStatus(): Promise<AuthorizationStatus> {
if (Platform.OS === 'android') {
return 'unavailable';
}
try {
const status = await ExpoFinanceKit.getAuthorizationStatus();
return status as AuthorizationStatus;
} catch (error) {
console.error('Error getting authorization status:', error);
return 'unavailable';
}
}
/**
* Checks if FinanceKit is available on the device
* @returns Boolean indicating availability
*/
export function isFinanceKitAvailable(): boolean {
if (Platform.OS === 'android') {
return false;
}
return ExpoFinanceKit.isAvailable || false;
}
/**
* Ensures user has authorized access before proceeding
* @param requestIfNeeded - Whether to request authorization if not determined
* @returns Promise resolving to boolean indicating authorization status
*/
export async function ensureAuthorized(requestIfNeeded: boolean = true): Promise<boolean> {
const status = await getAuthorizationStatus();
if (status === 'authorized') {
return true;
}
if (status === 'notDetermined' && requestIfNeeded) {
const { granted } = await requestAuthorization();
return granted;
}
return false;
}
/**
* Authorization status change listener manager
*/
class AuthorizationListener {
private listeners: Map<string, Function> = new Map();
/**
* Adds a listener for authorization status changes
* @param callback - Function to call when status changes
* @returns Function to remove the listener
*/
addStatusChangeListener(
callback: (payload: AuthorizationStatusChangedPayload) => void
): () => void {
const listenerId = Math.random().toString(36).substring(2, 11);
const listener = (payload: AuthorizationStatusChangedPayload) => {
callback({
...payload,
timestamp: payload.timestamp || Date.now(),
});
};
this.listeners.set(listenerId, listener);
if (ExpoFinanceKit.addListener) {
ExpoFinanceKit.addListener('onAuthorizationStatusChanged', listener);
}
return () => {
this.removeListener(listenerId);
};
}
/**
* Removes a specific listener
* @param listenerId - ID of the listener to remove
*/
private removeListener(listenerId: string): void {
const listener = this.listeners.get(listenerId);
if (listener && ExpoFinanceKit.removeListener) {
ExpoFinanceKit.removeListener('onAuthorizationStatusChanged', listener);
this.listeners.delete(listenerId);
}
}
/**
* Removes all authorization listeners
*/
removeAllListeners(): void {
if (ExpoFinanceKit.removeAllListeners) {
ExpoFinanceKit.removeAllListeners('onAuthorizationStatusChanged');
}
this.listeners.clear();
}
}
export const authorizationListener = new AuthorizationListener();
/**
* Waits for authorization with timeout
* @param timeoutMs - Maximum time to wait in milliseconds
* @returns Promise resolving to authorization result
*/
export async function waitForAuthorization(timeoutMs: number = 30000): Promise<AuthorizationResult> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
removeListener();
reject(createFinanceKitError(
FinanceKitErrorCode.Unknown,
'Authorization timeout'
));
}, timeoutMs);
const removeListener = authorizationListener.addStatusChangeListener((payload) => {
if (payload.status !== 'notDetermined') {
clearTimeout(timeout);
removeListener();
resolve({
granted: payload.status === 'authorized',
status: payload.status,
});
}
});
// Check current status immediately
getAuthorizationStatus().then(status => {
if (status !== 'notDetermined') {
clearTimeout(timeout);
removeListener();
resolve({
granted: status === 'authorized',
status,
});
}
});
});
}
/**
* Gets human-readable description for authorization status
* @param status - Authorization status
* @returns Human-readable description
*/
export function getAuthorizationStatusDescription(status: AuthorizationStatus): string {
switch (status) {
case 'authorized':
return 'Access to financial data has been granted';
case 'denied':
return 'Access to financial data has been denied';
case 'notDetermined':
return 'Authorization has not been requested yet';
case 'unavailable':
return 'FinanceKit is not available on this device';
default:
return 'Unknown authorization status';
}
}
/**
* Checks if authorization can be requested
* @returns Promise resolving to boolean
*/
export async function canRequestAuthorization(): Promise<boolean> {
if (!isFinanceKitAvailable()) {
return false;
}
const status = await getAuthorizationStatus();
return status === 'notDetermined';
}