diff --git a/backend/nitro.config.ts b/backend/nitro.config.ts index 641ad04..14511f6 100644 --- a/backend/nitro.config.ts +++ b/backend/nitro.config.ts @@ -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: { diff --git a/backend/server/services/voip-push.ts b/backend/server/services/voip-push.ts index 386b72c..d6fa3e4 100644 --- a/backend/server/services/voip-push.ts +++ b/backend/server/services/voip-push.ts @@ -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; +type ApnNotification = InstanceType; + +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 { 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 { - 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;