fix: keep bonjour rejection handler through shutdown
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
### Fixes
|
### Fixes
|
||||||
- Discord/Telegram: add per-request retry policy with configurable delays and docs.
|
- Discord/Telegram: add per-request retry policy with configurable delays and docs.
|
||||||
- macOS: prevent gateway launchd startup race where the app could kill a just-started gateway; avoid unnecessary `bootout` and ensure the job is enabled at login. Fixes #306. Thanks @gupsammy for PR #387.
|
- macOS: prevent gateway launchd startup race where the app could kill a just-started gateway; avoid unnecessary `bootout` and ensure the job is enabled at login. Fixes #306. Thanks @gupsammy for PR #387.
|
||||||
|
- macOS: ignore ciao announcement cancellation rejections during Bonjour shutdown to avoid unhandled exits. Thanks @emanuelst for PR #419.
|
||||||
- Pairing: generate DM pairing codes with CSPRNG, expire pending codes after 1 hour, and avoid re-sending codes for already pending requests.
|
- Pairing: generate DM pairing codes with CSPRNG, expire pending codes after 1 hour, and avoid re-sending codes for already pending requests.
|
||||||
- Pairing: lock + atomically write pairing stores with 0600 perms and stop logging pairing codes in provider logs.
|
- Pairing: lock + atomically write pairing stores with 0600 perms and stop logging pairing codes in provider logs.
|
||||||
- WhatsApp: add self-phone mode (no pairing replies for outbound DMs) and onboarding prompt for personal vs separate numbers (auto allowlist + response prefix for personal).
|
- WhatsApp: add self-phone mode (no pairing replies for outbound DMs) and onboarding prompt for personal vs separate numbers (auto allowlist + response prefix for personal).
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|||||||
|
|
||||||
const createService = vi.fn();
|
const createService = vi.fn();
|
||||||
const shutdown = vi.fn();
|
const shutdown = vi.fn();
|
||||||
|
const registerUnhandledRejectionHandler = vi.fn();
|
||||||
|
|
||||||
const logWarn = vi.fn();
|
const logWarn = vi.fn();
|
||||||
const logDebug = vi.fn();
|
const logDebug = vi.fn();
|
||||||
@@ -38,6 +39,14 @@ vi.mock("@homebridge/ciao", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mock("./unhandled-rejections.js", () => {
|
||||||
|
return {
|
||||||
|
registerUnhandledRejectionHandler: (
|
||||||
|
handler: (reason: unknown) => boolean,
|
||||||
|
) => registerUnhandledRejectionHandler(handler),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const { startGatewayBonjourAdvertiser } = await import("./bonjour.js");
|
const { startGatewayBonjourAdvertiser } = await import("./bonjour.js");
|
||||||
|
|
||||||
describe("gateway bonjour advertiser", () => {
|
describe("gateway bonjour advertiser", () => {
|
||||||
@@ -60,6 +69,7 @@ describe("gateway bonjour advertiser", () => {
|
|||||||
|
|
||||||
createService.mockReset();
|
createService.mockReset();
|
||||||
shutdown.mockReset();
|
shutdown.mockReset();
|
||||||
|
registerUnhandledRejectionHandler.mockReset();
|
||||||
logWarn.mockReset();
|
logWarn.mockReset();
|
||||||
logDebug.mockReset();
|
logDebug.mockReset();
|
||||||
getLoggerInfo.mockReset();
|
getLoggerInfo.mockReset();
|
||||||
@@ -177,6 +187,51 @@ describe("gateway bonjour advertiser", () => {
|
|||||||
await started.stop();
|
await started.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("cleans up unhandled rejection handler after shutdown", async () => {
|
||||||
|
// Allow advertiser to run in unit tests.
|
||||||
|
delete process.env.VITEST;
|
||||||
|
process.env.NODE_ENV = "development";
|
||||||
|
|
||||||
|
vi.spyOn(os, "hostname").mockReturnValue("test-host");
|
||||||
|
|
||||||
|
const destroy = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const advertise = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const order: string[] = [];
|
||||||
|
shutdown.mockImplementation(async () => {
|
||||||
|
order.push("shutdown");
|
||||||
|
});
|
||||||
|
|
||||||
|
createService.mockImplementation((options: Record<string, unknown>) => {
|
||||||
|
return {
|
||||||
|
advertise,
|
||||||
|
destroy,
|
||||||
|
serviceState: "announced",
|
||||||
|
on: vi.fn(),
|
||||||
|
getFQDN: () =>
|
||||||
|
`${asString(options.type, "service")}.${asString(options.domain, "local")}.`,
|
||||||
|
getHostname: () => asString(options.hostname, "unknown"),
|
||||||
|
getPort: () => Number(options.port ?? -1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanup = vi.fn(() => {
|
||||||
|
order.push("cleanup");
|
||||||
|
});
|
||||||
|
registerUnhandledRejectionHandler.mockImplementation(() => cleanup);
|
||||||
|
|
||||||
|
const started = await startGatewayBonjourAdvertiser({
|
||||||
|
gatewayPort: 18789,
|
||||||
|
sshPort: 2222,
|
||||||
|
bridgePort: 18790,
|
||||||
|
});
|
||||||
|
|
||||||
|
await started.stop();
|
||||||
|
|
||||||
|
expect(registerUnhandledRejectionHandler).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cleanup).toHaveBeenCalledTimes(1);
|
||||||
|
expect(order).toEqual(["shutdown", "cleanup"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("logs advertise failures and retries via watchdog", async () => {
|
it("logs advertise failures and retries via watchdog", async () => {
|
||||||
// Allow advertiser to run in unit tests.
|
// Allow advertiser to run in unit tests.
|
||||||
delete process.env.VITEST;
|
delete process.env.VITEST;
|
||||||
|
|||||||
@@ -267,11 +267,12 @@ export async function startGatewayBonjourAdvertiser(
|
|||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ciaoCancellationRejectionHandler?.();
|
|
||||||
try {
|
try {
|
||||||
await responder.shutdown();
|
await responder.shutdown();
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
|
} finally {
|
||||||
|
ciaoCancellationRejectionHandler?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user