- App.tsx: full navigation (Auth stack + Main tabs with 5 screens) - Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen - HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors - MapScreen: interactive map with layer toggles (6 layers) - MarketplaceScreen: categories (6), products (5), search - ChatScreen: AI chat with quick prompts (4), bot responses - ProfileScreen: user info, stats, menu (9 items), logout - AlertsScreen: alert list with severity, acknowledge - SensorsScreen: sensor list with type filters (6 types), search - ZonesScreen: zone cards with stats - SettingsScreen: language picker (FR/EN/ES/DE), privacy, about - Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n - Hooks: useSensors, useAlerts, useNotifications, useLocation - Components: Card, Button, LoadingSpinner, ErrorBoundary, Header - Services: iotService, notificationService (with axios API client) - Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN) - Theme: colors.ts with full design system (Blue Ocean palette) - Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
239 lines
7.5 KiB
TypeScript
239 lines
7.5 KiB
TypeScript
import * as Application from 'expo-application';
|
|
import Constants from 'expo-constants';
|
|
import { Platform, CodedError, UnavailabilityError } from 'expo-modules-core';
|
|
|
|
import { setAutoServerRegistrationEnabledAsync } from './DevicePushTokenAutoRegistration.fx';
|
|
import ServerRegistrationModule from './ServerRegistrationModule';
|
|
import { DevicePushToken, ExpoPushToken, ExpoPushTokenOptions } from './Tokens.types';
|
|
import getDevicePushTokenAsync from './getDevicePushTokenAsync';
|
|
|
|
const productionBaseUrl = 'https://exp.host/--/api/v2/';
|
|
|
|
/**
|
|
* Returns an Expo token that can be used to send a push notification to the device using Expo's push notifications service.
|
|
*
|
|
* This method makes requests to the Expo's servers. It can get rejected in cases where the request itself fails
|
|
* (for example, due to the device being offline, experiencing a network timeout, or other HTTPS request failures).
|
|
* To provide offline support to your users, you should `try/catch` this method and implement retry logic to attempt
|
|
* to get the push token later, once the device is back online.
|
|
*
|
|
* > For Expo's backend to be able to send notifications to your app, you will need to provide it with push notification keys.
|
|
* For more information, see [credentials](/push-notifications/push-notifications-setup/#get-credentials-for-development-builds) in the push notifications setup.
|
|
*
|
|
* @param options Object allowing you to pass in push notification configuration.
|
|
* @return Returns a `Promise` that resolves to an object representing acquired push token.
|
|
* @header fetch
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import * as Notifications from 'expo-notifications';
|
|
*
|
|
* export async function registerForPushNotificationsAsync(userId: string) {
|
|
* const expoPushToken = await Notifications.getExpoPushTokenAsync({
|
|
* projectId: 'your-project-id',
|
|
* });
|
|
*
|
|
* await fetch('https://example.com/', {
|
|
* method: 'POST',
|
|
* headers: {
|
|
* 'Content-Type': 'application/json',
|
|
* },
|
|
* body: JSON.stringify({
|
|
* userId,
|
|
* expoPushToken,
|
|
* }),
|
|
* });
|
|
* }
|
|
* ```
|
|
*/
|
|
export default async function getExpoPushTokenAsync(
|
|
options: ExpoPushTokenOptions = {}
|
|
): Promise<ExpoPushToken> {
|
|
const devicePushToken = options.devicePushToken || (await getDevicePushTokenAsync());
|
|
|
|
const deviceId = options.deviceId || (await getDeviceIdAsync());
|
|
const projectId = options.projectId || Constants.easConfig?.projectId;
|
|
|
|
if (!projectId) {
|
|
console.warn(
|
|
'Calling getExpoPushTokenAsync without specifying a projectId is deprecated and will no longer be supported in SDK 49+'
|
|
);
|
|
}
|
|
|
|
if (!projectId) {
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_NO_EXPERIENCE_ID',
|
|
"No 'projectId' found. If 'projectId' can't be inferred from the manifest (eg. in bare workflow), you have to pass it in yourself."
|
|
);
|
|
}
|
|
|
|
const applicationId = options.applicationId || Application.applicationId;
|
|
if (!applicationId) {
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_NO_APPLICATION_ID',
|
|
"No applicationId found. If it can't be inferred from native configuration by expo-application, you have to pass it in yourself."
|
|
);
|
|
}
|
|
const type = options.type || getTypeOfToken(devicePushToken);
|
|
const development = options.development || (await shouldUseDevelopmentNotificationService());
|
|
|
|
const baseUrl = options.baseUrl ?? productionBaseUrl;
|
|
const url = options.url ?? `${baseUrl}push/getExpoPushToken`;
|
|
|
|
const body = {
|
|
type,
|
|
deviceId: deviceId.toLowerCase(),
|
|
development,
|
|
appId: applicationId,
|
|
deviceToken: getDeviceToken(devicePushToken),
|
|
projectId,
|
|
};
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
},
|
|
body: JSON.stringify(body),
|
|
}).catch((error) => {
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_NETWORK_ERROR',
|
|
`Error encountered while fetching Expo token: ${error}.`
|
|
);
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const statusInfo = response.statusText || response.status;
|
|
let body: string | undefined = undefined;
|
|
try {
|
|
body = await response.text();
|
|
} catch {
|
|
// do nothing
|
|
}
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_SERVER_ERROR',
|
|
`Error encountered while fetching Expo token, expected an OK response, received: ${statusInfo} (body: "${body}").`
|
|
);
|
|
}
|
|
|
|
const expoPushToken = getExpoPushToken(await parseResponse(response));
|
|
|
|
try {
|
|
if (options.url || options.baseUrl) {
|
|
console.debug(
|
|
`[expo-notifications] Since the URL endpoint to register in has been customized in the options, expo-notifications won't try to auto-update the device push token on the server.`
|
|
);
|
|
} else {
|
|
await setAutoServerRegistrationEnabledAsync(true);
|
|
}
|
|
} catch (e) {
|
|
console.warn(
|
|
'[expo-notifications] Could not enable automatically registering new device tokens with the Expo notification service',
|
|
e
|
|
);
|
|
}
|
|
|
|
return {
|
|
type: 'expo',
|
|
data: expoPushToken,
|
|
};
|
|
}
|
|
|
|
async function parseResponse(response: Response) {
|
|
try {
|
|
return await response.json();
|
|
} catch {
|
|
try {
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_SERVER_ERROR',
|
|
`Expected a JSON response from server when fetching Expo token, received body: ${JSON.stringify(
|
|
await response.text()
|
|
)}.`
|
|
);
|
|
} catch {
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_SERVER_ERROR',
|
|
`Expected a JSON response from server when fetching Expo token, received response: ${JSON.stringify(
|
|
response
|
|
)}.`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getExpoPushToken(data: any) {
|
|
if (
|
|
!data ||
|
|
!(typeof data === 'object') ||
|
|
!data.data ||
|
|
!(typeof data.data === 'object') ||
|
|
!data.data.expoPushToken ||
|
|
!(typeof data.data.expoPushToken === 'string')
|
|
) {
|
|
throw new CodedError(
|
|
'ERR_NOTIFICATIONS_SERVER_ERROR',
|
|
`Malformed response from server, expected "{ data: { expoPushToken: string } }", received: ${JSON.stringify(
|
|
data,
|
|
null,
|
|
2
|
|
)}.`
|
|
);
|
|
}
|
|
|
|
return data.data.expoPushToken as string;
|
|
}
|
|
|
|
// Same as in DevicePushTokenAutoRegistration
|
|
async function getDeviceIdAsync() {
|
|
try {
|
|
if (!ServerRegistrationModule.getInstallationIdAsync) {
|
|
throw new UnavailabilityError('ExpoServerRegistrationModule', 'getInstallationIdAsync');
|
|
}
|
|
|
|
return await ServerRegistrationModule.getInstallationIdAsync();
|
|
} catch (e) {
|
|
throw new CodedError(
|
|
'ERR_NOTIF_DEVICE_ID',
|
|
`Could not have fetched installation ID of the application: ${e}.`
|
|
);
|
|
}
|
|
}
|
|
|
|
function getDeviceToken(devicePushToken: DevicePushToken) {
|
|
if (typeof devicePushToken.data === 'string') {
|
|
return devicePushToken.data;
|
|
}
|
|
|
|
return JSON.stringify(devicePushToken.data);
|
|
}
|
|
|
|
// Same as in DevicePushTokenAutoRegistration
|
|
async function shouldUseDevelopmentNotificationService() {
|
|
if (Platform.OS === 'ios') {
|
|
try {
|
|
const notificationServiceEnvironment =
|
|
await Application.getIosPushNotificationServiceEnvironmentAsync();
|
|
if (notificationServiceEnvironment === 'development') {
|
|
return true;
|
|
}
|
|
} catch {
|
|
// We can't do anything here, we'll fallback to false then.
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Same as in DevicePushTokenAutoRegistration
|
|
function getTypeOfToken(devicePushToken: DevicePushToken) {
|
|
switch (devicePushToken.type) {
|
|
case 'ios':
|
|
return 'apns';
|
|
case 'android':
|
|
return 'fcm';
|
|
// This probably will error on server, but let's make this function future-safe.
|
|
default:
|
|
return devicePushToken.type;
|
|
}
|
|
}
|