fix(voip-push): dynamic import @parse/node-apn — nitro bundler bricht statisches Tracing (Class extends Module-namespace)

This commit is contained in:
chahinebrini 2026-06-04 10:40:44 +02:00
parent 57e0a23021
commit 848b517d22
2 changed files with 26 additions and 15 deletions

View File

@ -10,15 +10,13 @@ export default defineNitroConfig({
// Default-publicAssets greift nicht zuverlässig wenn srcDir auf "server" zeigt.
publicAssets: [{ baseURL: "/", dir: "../public", maxAge: 60 * 60 }],
// Supabase + @parse/node-apn + imapflow als external deps — nicht bundlen.
// node-apn + imapflow nutzen CJS-extends/inherits-Patterns, die brechen beim
// Bundlen zu ESM ("Class extends value [object Module] is not a constructor
// or null" / "superCtor.prototype must be of type object"). Müssen als externe
// node_modules-Requires bleiben.
// Supabase + imapflow als external deps — nicht bundlen.
// imapflow nutzt CJS-inherits-Pattern, bricht beim Bundlen zu ESM
// ("superCtor.prototype must be of type object"). @parse/node-apn wird
// in services/voip-push.ts via dynamic import geladen (vermeidet das gleiche
// Problem ohne Externalize-Eintrag).
externals: {
inline: [
/^(?!@supabase\/supabase-js)(?!@parse\/node-apn)(?!imapflow)/,
],
inline: [/^(?!@supabase\/supabase-js)(?!imapflow)/],
},
imports: {

View File

@ -17,14 +17,22 @@
* Wenn ENV-Vars fehlen: Service no-op (kein Error, nur Log-Warnung beim ersten Send).
* Macht reguläre Pushes nicht kaputt wenn das VoIP-Setup noch unfertig ist.
*/
import apn from "@parse/node-apn";
import fs from "node:fs";
let provider: apn.Provider | null = null;
// node-apn dynamisch laden (await import statt static import). Grund: nitro/rollup
// bricht beim Bundlen von node-apn → undici → `Class extends value [object Module]`
// at push.mjs. Dynamic import wird nicht statisch getraced, lädt Modul zur Laufzeit
// aus node_modules wo es korrekt als CJS funktioniert.
type ApnModule = typeof import("@parse/node-apn");
type ApnProvider = InstanceType<ApnModule["Provider"]>;
type ApnNotification = InstanceType<ApnModule["Notification"]>;
let apnMod: ApnModule | null = null;
let provider: ApnProvider | null = null;
let initialized = false;
let topic: string | null = null;
function getProvider(): apn.Provider | null {
async function getProvider(): Promise<ApnProvider | null> {
if (initialized) return provider;
initialized = true;
@ -45,8 +53,13 @@ function getProvider(): apn.Provider | null {
}
try {
// Dynamic import — vermeidet bundler statisches Tracing.
const mod = (await import("@parse/node-apn")) as unknown as ApnModule & {
default?: ApnModule;
};
apnMod = mod.default ?? mod;
topic = tpc;
provider = new apn.Provider({
provider = new apnMod.Provider({
pfx: p12Path,
passphrase: p12Pass,
production,
@ -84,10 +97,10 @@ export interface VoIPCallPayload {
* @returns true bei Erfolg (oder no-op wenn Service disabled), false bei Fehler.
*/
export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
const p = getProvider();
if (!p || !topic) return true; // no-op, regulärer Push übernimmt
const p = await getProvider();
if (!p || !topic || !apnMod) return true; // no-op, regulärer Push übernimmt
const note = new apn.Notification();
const note: ApnNotification = new apnMod.Notification();
note.topic = topic;
note.expiry = 0; // sofort verwerfen wenn Device unreachable
note.priority = 10;