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

View File

@ -17,14 +17,22 @@
* Wenn ENV-Vars fehlen: Service no-op (kein Error, nur Log-Warnung beim ersten Send). * 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. * Macht reguläre Pushes nicht kaputt wenn das VoIP-Setup noch unfertig ist.
*/ */
import apn from "@parse/node-apn";
import fs from "node:fs"; 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 initialized = false;
let topic: string | null = null; let topic: string | null = null;
function getProvider(): apn.Provider | null { async function getProvider(): Promise<ApnProvider | null> {
if (initialized) return provider; if (initialized) return provider;
initialized = true; initialized = true;
@ -45,8 +53,13 @@ function getProvider(): apn.Provider | null {
} }
try { 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; topic = tpc;
provider = new apn.Provider({ provider = new apnMod.Provider({
pfx: p12Path, pfx: p12Path,
passphrase: p12Pass, passphrase: p12Pass,
production, production,
@ -84,10 +97,10 @@ export interface VoIPCallPayload {
* @returns true bei Erfolg (oder no-op wenn Service disabled), false bei Fehler. * @returns true bei Erfolg (oder no-op wenn Service disabled), false bei Fehler.
*/ */
export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> { export async function sendVoIPPush(payload: VoIPCallPayload): Promise<boolean> {
const p = getProvider(); const p = await getProvider();
if (!p || !topic) return true; // no-op, regulärer Push übernimmt 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.topic = topic;
note.expiry = 0; // sofort verwerfen wenn Device unreachable note.expiry = 0; // sofort verwerfen wenn Device unreachable
note.priority = 10; note.priority = 10;