Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/lib/push-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import type { ClientMessage } from "@codeoid/protocol";
* only when a push transport is configured. Matches `CAPABILITIES.PUSH`. */
export const PUSH_CAPABILITY = "push";

/** Advertised by the daemon when it delivers via NATIVE APNs/FCM tokens
* (transport `native` or `relay`) rather than Expo — the client then registers
* its `getDevicePushTokenAsync` token instead of the Expo token. Matches
* `CAPABILITIES.PUSH_NATIVE`. */
export const PUSH_NATIVE_CAPABILITY = "push.native";

export type PushPlatform = "ios" | "android";

export interface PushRegisterMsg {
Expand Down
33 changes: 25 additions & 8 deletions src/lib/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import * as Notifications from "expo-notifications";
import { Platform } from "react-native";

import type { Connection } from "./connection";
import { PUSH_CAPABILITY, sendPushMsg, type PushPlatform } from "./push-protocol";
import {
PUSH_CAPABILITY,
PUSH_NATIVE_CAPABILITY,
sendPushMsg,
type PushPlatform,
} from "./push-protocol";

/** Token registered this session, so sign-out can unregister exactly it. */
let registeredToken: string | null = null;
Expand Down Expand Up @@ -52,9 +57,13 @@ export async function registerForPush(
): Promise<void> {
const platform = nativePlatform();
if (!platform) return;
// Only register when the daemon will actually deliver — avoids a needless
// permission prompt against a daemon with no push transport configured.
if (!daemonCapabilities?.includes(PUSH_CAPABILITY)) return;
// Which token type does the daemon want? `push.native` (transport native/relay
// → APNs/FCM directly) wants this device's NATIVE token; `push` (transport
// expo) wants an Expo token. Neither advertised → the daemon has no push
// transport configured, so skip (avoids a needless permission prompt).
const wantsNative = daemonCapabilities?.includes(PUSH_NATIVE_CAPABILITY) ?? false;
const wantsExpo = daemonCapabilities?.includes(PUSH_CAPABILITY) ?? false;
if (!wantsNative && !wantsExpo) return;
if (!Device.isDevice) return; // simulators/emulators can't get a push token

try {
Expand All @@ -64,10 +73,18 @@ export async function registerForPush(
}
if (!perm.granted) return;

const projectId = easProjectId();
const { data: token } = await Notifications.getExpoPushTokenAsync(
projectId ? { projectId } : undefined,
);
let token: string;
if (wantsNative) {
// Raw APNs (iOS) / FCM (Android) device token — the self-hosted daemon or
Comment thread
saucam marked this conversation as resolved.
// relay sends via APNs/FCM directly. No EAS projectId needed.
// `DevicePushToken.data` is typed `any` (the ImplicitlySupportedDevicePushToken
// union arm), not `string`, so coerce to guarantee a string at runtime.
const device = await Notifications.getDevicePushTokenAsync();
token = typeof device.data === "string" ? device.data : String(device.data);
} else {
const projectId = easProjectId();
token = (await Notifications.getExpoPushTokenAsync(projectId ? { projectId } : undefined)).data;
}
if (conn.client.status.kind !== "connected") return;
sendPushMsg(conn.client, { type: "push.register", id: conn.client.nextId(), token, platform });
registeredToken = token;
Expand Down
Loading