惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
IT之家
IT之家
博客园 - 聂微东
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
Last Week in AI
Last Week in AI
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
小众软件
小众软件
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
阮一峰的网络日志
阮一峰的网络日志
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
量子位
月光博客
月光博客
博客园 - 【当耐特】
博客园 - 叶小钗

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(apns): cap direct timeout paths · openclaw/openclaw@f...
steipete · 2026-05-30 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -1,5 +1,6 @@

11

import type http2 from "node:http2";

22

import { beforeEach, describe, expect, it, vi } from "vitest";

3+

import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";

34

import type { HttpConnectTunnelParams } from "./net/http-connect-tunnel.js";

45

import {

56

resetActiveManagedProxyStateForTests,

@@ -193,6 +194,21 @@ describe("connectApnsHttp2Session", () => {

193194

expect(createConnection?.(new URL("https://api.push.apple.com"), {})).toBe(fakeTlsSocket);

194195

});

195196
197+

it("caps oversized managed proxy timeouts before opening the APNs tunnel", async () => {

198+

const registration = registerActiveManagedProxyUrl(new URL("https://proxy.example:8443"), {

199+

loopbackMode: "gateway-only",

200+

});

201+

const { connectApnsHttp2Session } = await import("./push-apns-http2.js");

202+
203+

await connectApnsHttp2Session({

204+

authority: "https://api.push.apple.com",

205+

timeoutMs: Number.MAX_SAFE_INTEGER,

206+

});

207+

stopActiveManagedProxyRegistration(registration);

208+
209+

expect(lastTunnelCall().timeoutMs).toBe(MAX_TIMER_TIMEOUT_MS);

210+

});

211+
196212

it("ignores ambient proxy env when managed proxy is inactive", async () => {

197213

const originalHttpsProxy = process.env["HTTPS_PROXY"];

198214

process.env["HTTPS_PROXY"] = "http://ambient.example:8080";

@@ -252,6 +268,18 @@ describe("connectApnsHttp2Session", () => {

252268

expect(fakeSession.close).toHaveBeenCalledOnce();

253269

});

254270
271+

it("caps oversized explicit proxy probe timeouts", async () => {

272+

const { probeApnsHttp2ReachabilityViaProxy } = await import("./push-apns-http2.js");

273+
274+

await probeApnsHttp2ReachabilityViaProxy({

275+

authority: "https://api.sandbox.push.apple.com",

276+

proxyUrl: "http://proxy.example:8080",

277+

timeoutMs: Number.MAX_SAFE_INTEGER,

278+

});

279+
280+

expect(lastTunnelCall().timeoutMs).toBe(MAX_TIMER_TIMEOUT_MS);

281+

});

282+
255283

it("rejects non-APNs authorities", async () => {

256284

const { connectApnsHttp2Session, probeApnsHttp2ReachabilityViaProxy } =

257285

await import("./push-apns-http2.js");

Original file line numberDiff line numberDiff line change

@@ -1,4 +1,5 @@

11

import http2 from "node:http2";

2+

import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";

23

import { openHttpConnectTunnel } from "./net/http-connect-tunnel.js";

34

import {

45

getActiveManagedProxyUrl,

@@ -17,6 +18,7 @@ const APNS_AUTHORITIES = new Set([

1718

type ApnsAuthority = "https://api.push.apple.com" | "https://api.sandbox.push.apple.com";

1819
1920

export const APNS_HTTP2_CANCEL_CODE = http2.constants.NGHTTP2_CANCEL;

21+

const APNS_HTTP2_MIN_TIMEOUT_MS = 1000;

2022
2123

export type ConnectApnsHttp2SessionParams = {

2224

authority: string;

@@ -85,6 +87,7 @@ export async function connectApnsHttp2Session(

8587

params: ConnectApnsHttp2SessionParams,

8688

): Promise<http2.ClientHttp2Session> {

8789

const authority = assertApnsAuthority(params.authority);

90+

const timeoutMs = resolveApnsHttp2TimeoutMs(params.timeoutMs);

8891

const proxyUrl = getActiveManagedProxyUrl();

8992

if (!proxyUrl) {

9093

return http2.connect(authority);

@@ -94,19 +97,24 @@ export async function connectApnsHttp2Session(

9497

authority,

9598

proxyUrl,

9699

proxyTls: getActiveManagedProxyTlsOptions(),

97-

timeoutMs: params.timeoutMs,

100+

timeoutMs,

98101

});

99102

}

100103
104+

function resolveApnsHttp2TimeoutMs(timeoutMs: number): number {

105+

return resolveTimerTimeoutMs(timeoutMs, APNS_HTTP2_MIN_TIMEOUT_MS, APNS_HTTP2_MIN_TIMEOUT_MS);

106+

}

107+
101108

export async function probeApnsHttp2ReachabilityViaProxy(

102109

params: ProbeApnsHttp2ReachabilityViaProxyParams,

103110

): Promise<ProbeApnsHttp2ReachabilityViaProxyResult> {

104111

const authority = assertApnsAuthority(params.authority);

112+

const timeoutMs = resolveApnsHttp2TimeoutMs(params.timeoutMs);

105113

const session = await openProxiedApnsHttp2Session({

106114

authority,

107115

proxyUrl: new URL(params.proxyUrl),

108116

...(params.proxyTls ? { proxyTls: params.proxyTls } : {}),

109-

timeoutMs: params.timeoutMs,

117+

timeoutMs,

110118

});

111119
112120

try {

@@ -116,10 +124,8 @@ export async function probeApnsHttp2ReachabilityViaProxy(

116124

let status: number | undefined;

117125

let responseHeaders: Record<string, string> = {};

118126

const timeout = setTimeout(() => {

119-

fail(

120-

new Error(`APNs reachability probe timed out after ${Math.trunc(params.timeoutMs)}ms`),

121-

);

122-

}, Math.trunc(params.timeoutMs));

127+

fail(new Error(`APNs reachability probe timed out after ${timeoutMs}ms`));

128+

}, timeoutMs);

123129

timeout.unref?.();

124130
125131

const cleanup = () => {

Original file line numberDiff line numberDiff line change

@@ -3,6 +3,7 @@ import { createServer, type Server as HttpServer } from "node:http";

33

import http2 from "node:http2";

44

import net from "node:net";

55

import { afterEach, describe, expect, it, vi } from "vitest";

6+

import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";

67

import { startProxy, stopProxy, type ProxyHandle } from "./net/proxy/proxy-lifecycle.js";

78

import {

89

sendApnsAlert,

@@ -531,6 +532,30 @@ describe("push APNs send semantics", () => {

531532

});

532533

});

533534
535+

it("caps oversized direct send timeouts", async () => {

536+

const { send, registration, auth } = createDirectApnsSendFixture({

537+

nodeId: "ios-node-direct-timeout-cap",

538+

environment: "sandbox",

539+

sendResult: {

540+

status: 200,

541+

apnsId: "apns-timeout-cap-id",

542+

body: "{}",

543+

},

544+

});

545+
546+

await sendApnsAlert({

547+

registration,

548+

nodeId: "ios-node-direct-timeout-cap",

549+

title: "Wake",

550+

body: "Ping",

551+

auth,

552+

requestSender: send,

553+

timeoutMs: Number.MAX_SAFE_INTEGER,

554+

});

555+
556+

expect(requireSendRequest(send).timeoutMs).toBe(MAX_TIMER_TIMEOUT_MS);

557+

});

558+
534559

it("fails closed before sending when direct registrations carry invalid topics", async () => {

535560

const { send, registration, auth } = createDirectApnsSendFixture({

536561

nodeId: "ios-node-invalid-topic",

Original file line numberDiff line numberDiff line change

@@ -2,6 +2,7 @@ import { createHash, createPrivateKey, sign as signJwt } from "node:crypto";

22

import fs from "node:fs/promises";

33

import path from "node:path";

44

import { resolveStateDir } from "../config/paths.js";

5+

import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";

56

import {

67

normalizeLowercaseStringOrEmpty,

78

normalizeOptionalString,

@@ -766,9 +767,7 @@ async function sendApnsRequest(params: {

766767

}

767768
768769

function resolveApnsTimeoutMs(timeoutMs: number | undefined): number {

769-

return typeof timeoutMs === "number" && Number.isFinite(timeoutMs)

770-

? Math.max(1000, Math.trunc(timeoutMs))

771-

: DEFAULT_APNS_TIMEOUT_MS;

770+

return resolveTimerTimeoutMs(timeoutMs, DEFAULT_APNS_TIMEOUT_MS, 1000);

772771

}

773772
774773

function resolveDirectSendContext(params: {