hx-cdn-forge 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +803 -0
- package/dist/index.css +504 -0
- package/dist/index.d.mts +328 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.js +1405 -0
- package/dist/index.mjs +1380 -0
- package/dist/split.js +309 -0
- package/package.json +89 -0
- package/src/cli/split.ts +311 -0
- package/src/core/__tests__/cdnNodes.test.ts +142 -0
- package/src/core/__tests__/manifest.test.ts +117 -0
- package/src/core/cdnNodes.ts +251 -0
- package/src/core/chunkedFetcher.ts +529 -0
- package/src/core/config.ts +66 -0
- package/src/core/fetcher.ts +476 -0
- package/src/core/manifest.ts +174 -0
- package/src/index.ts +73 -0
- package/src/react/CDNContext.tsx +268 -0
- package/src/react/CDNNodeSelector/index.tsx +258 -0
- package/src/react/CDNNodeSelector/styles.css +578 -0
- package/src/types.ts +316 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
|
|
3
|
+
type CDNRegion = 'china' | 'asia' | 'global';
|
|
4
|
+
type LatencyStatus = 'idle' | 'testing' | 'success' | 'failed';
|
|
5
|
+
interface CDNNode {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
region: CDNRegion;
|
|
10
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
11
|
+
maxFileSize: number;
|
|
12
|
+
supportsRange: boolean;
|
|
13
|
+
testPath?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface GitHubContext {
|
|
18
|
+
user: string;
|
|
19
|
+
repo: string;
|
|
20
|
+
ref: string;
|
|
21
|
+
}
|
|
22
|
+
interface ForgeConfig {
|
|
23
|
+
github: GitHubContext;
|
|
24
|
+
nodes?: CDNNode[];
|
|
25
|
+
defaultNodeId?: string;
|
|
26
|
+
autoTest?: boolean;
|
|
27
|
+
testTimeout?: number;
|
|
28
|
+
testRetries?: number;
|
|
29
|
+
splitThreshold?: number;
|
|
30
|
+
mappingPrefix?: string;
|
|
31
|
+
splitStoragePath?: string;
|
|
32
|
+
storageKey?: string;
|
|
33
|
+
maxConcurrency?: number;
|
|
34
|
+
chunkTimeout?: number;
|
|
35
|
+
maxRetries?: number;
|
|
36
|
+
enableWorkStealing?: boolean;
|
|
37
|
+
turboMode?: boolean;
|
|
38
|
+
turboConcurrentCDNs?: number;
|
|
39
|
+
}
|
|
40
|
+
interface SplitInfo {
|
|
41
|
+
originalName: string;
|
|
42
|
+
totalSize: number;
|
|
43
|
+
mimeType: string;
|
|
44
|
+
chunks: SplitChunkInfo[];
|
|
45
|
+
createdAt: string;
|
|
46
|
+
chunkSize: number;
|
|
47
|
+
}
|
|
48
|
+
interface SplitChunkInfo {
|
|
49
|
+
fileName: string;
|
|
50
|
+
index: number;
|
|
51
|
+
size: number;
|
|
52
|
+
sha256: string;
|
|
53
|
+
}
|
|
54
|
+
interface SplitCache {
|
|
55
|
+
sourcePath: string;
|
|
56
|
+
sourceHash: string;
|
|
57
|
+
sourceSize: number;
|
|
58
|
+
generatedAt: string;
|
|
59
|
+
}
|
|
60
|
+
interface LatencyResult {
|
|
61
|
+
nodeId: string;
|
|
62
|
+
latency: number;
|
|
63
|
+
success: boolean;
|
|
64
|
+
timestamp: number;
|
|
65
|
+
error?: string;
|
|
66
|
+
}
|
|
67
|
+
interface CDNNodeWithLatency extends CDNNode {
|
|
68
|
+
latency?: number;
|
|
69
|
+
latencyStatus?: LatencyStatus;
|
|
70
|
+
}
|
|
71
|
+
interface DownloadProgress {
|
|
72
|
+
loaded: number;
|
|
73
|
+
total: number;
|
|
74
|
+
percentage: number;
|
|
75
|
+
speed: number;
|
|
76
|
+
eta: number;
|
|
77
|
+
completedChunks: number;
|
|
78
|
+
totalChunks: number;
|
|
79
|
+
}
|
|
80
|
+
interface DownloadResult {
|
|
81
|
+
blob: Blob;
|
|
82
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
83
|
+
totalSize: number;
|
|
84
|
+
totalTime: number;
|
|
85
|
+
contentType: string;
|
|
86
|
+
usedSplitMode: boolean;
|
|
87
|
+
usedParallelMode: boolean;
|
|
88
|
+
nodeContributions: Map<string, {
|
|
89
|
+
bytes: number;
|
|
90
|
+
chunks: number;
|
|
91
|
+
avgSpeed: number;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
interface CDNProviderProps {
|
|
95
|
+
config: ForgeConfig;
|
|
96
|
+
children: React.ReactNode;
|
|
97
|
+
onInitialized?: (currentNode: CDNNode | null) => void;
|
|
98
|
+
onNodeChange?: (node: CDNNode) => void;
|
|
99
|
+
}
|
|
100
|
+
interface CDNContextValue {
|
|
101
|
+
config: ForgeConfig;
|
|
102
|
+
currentNode: CDNNode | null;
|
|
103
|
+
nodes: CDNNodeWithLatency[];
|
|
104
|
+
isTesting: boolean;
|
|
105
|
+
isInitialized: boolean;
|
|
106
|
+
latencyResults: Map<string, LatencyResult>;
|
|
107
|
+
selectNode: (nodeId: string) => void;
|
|
108
|
+
testAllNodes: () => Promise<LatencyResult[]>;
|
|
109
|
+
reqByCDN: (filePath: string, onProgress?: (p: DownloadProgress) => void) => Promise<DownloadResult>;
|
|
110
|
+
buildUrl: (filePath: string) => string;
|
|
111
|
+
getSortedNodes: () => CDNNodeWithLatency[];
|
|
112
|
+
}
|
|
113
|
+
interface CDNNodeSelectorProps {
|
|
114
|
+
className?: string;
|
|
115
|
+
style?: React.CSSProperties;
|
|
116
|
+
showLatency?: boolean;
|
|
117
|
+
showRegion?: boolean;
|
|
118
|
+
title?: string;
|
|
119
|
+
showRefreshButton?: boolean;
|
|
120
|
+
disabled?: boolean;
|
|
121
|
+
compact?: boolean;
|
|
122
|
+
onChange?: (node: CDNNode) => void;
|
|
123
|
+
onTestComplete?: (results: LatencyResult[]) => void;
|
|
124
|
+
renderTrigger?: (props: {
|
|
125
|
+
currentNode: CDNNode | null;
|
|
126
|
+
isOpen: boolean;
|
|
127
|
+
isTesting: boolean;
|
|
128
|
+
}) => React.ReactNode;
|
|
129
|
+
renderNode?: (props: NodeRenderProps) => React.ReactNode;
|
|
130
|
+
renderEmpty?: () => React.ReactNode;
|
|
131
|
+
renderLoading?: () => React.ReactNode;
|
|
132
|
+
}
|
|
133
|
+
interface NodeRenderProps {
|
|
134
|
+
node: CDNNodeWithLatency;
|
|
135
|
+
isSelected: boolean;
|
|
136
|
+
isDisabled: boolean;
|
|
137
|
+
latencyText: string;
|
|
138
|
+
latencyClassName: string;
|
|
139
|
+
onSelect: () => void;
|
|
140
|
+
}
|
|
141
|
+
interface SplitCommandOptions {
|
|
142
|
+
source: string;
|
|
143
|
+
outputDir: string;
|
|
144
|
+
mappingPrefix: string;
|
|
145
|
+
chunkSize?: number;
|
|
146
|
+
force?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
declare const DEFAULTS: {
|
|
150
|
+
readonly splitThreshold: number;
|
|
151
|
+
readonly chunkSizeForSplit: number;
|
|
152
|
+
readonly testTimeout: 5000;
|
|
153
|
+
readonly testRetries: 2;
|
|
154
|
+
readonly maxConcurrency: 6;
|
|
155
|
+
readonly chunkTimeout: 30000;
|
|
156
|
+
readonly maxRetries: 3;
|
|
157
|
+
readonly enableWorkStealing: true;
|
|
158
|
+
readonly turboMode: false;
|
|
159
|
+
readonly turboConcurrentCDNs: 3;
|
|
160
|
+
readonly storageKey: "hx-cdn-forge-node";
|
|
161
|
+
readonly autoTest: true;
|
|
162
|
+
};
|
|
163
|
+
declare function normalizeConfig(config: ForgeConfig): Required<ForgeConfig>;
|
|
164
|
+
declare function createForgeConfig(github: GitHubContext, options?: Partial<Omit<ForgeConfig, 'github'>>): ForgeConfig;
|
|
165
|
+
|
|
166
|
+
declare const CDN_NODE_PRESETS: {
|
|
167
|
+
jsdelivr_main: {
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
baseUrl: string;
|
|
171
|
+
region: "global";
|
|
172
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
173
|
+
maxFileSize: number;
|
|
174
|
+
supportsRange: true;
|
|
175
|
+
description: string;
|
|
176
|
+
};
|
|
177
|
+
jsdelivr_fastly: {
|
|
178
|
+
id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
baseUrl: string;
|
|
181
|
+
region: "global";
|
|
182
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
183
|
+
maxFileSize: number;
|
|
184
|
+
supportsRange: true;
|
|
185
|
+
description: string;
|
|
186
|
+
};
|
|
187
|
+
jsdelivr_testing: {
|
|
188
|
+
id: string;
|
|
189
|
+
name: string;
|
|
190
|
+
baseUrl: string;
|
|
191
|
+
region: "global";
|
|
192
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
193
|
+
maxFileSize: number;
|
|
194
|
+
supportsRange: true;
|
|
195
|
+
description: string;
|
|
196
|
+
};
|
|
197
|
+
jsd_mirror: {
|
|
198
|
+
id: string;
|
|
199
|
+
name: string;
|
|
200
|
+
baseUrl: string;
|
|
201
|
+
region: "china";
|
|
202
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
203
|
+
maxFileSize: number;
|
|
204
|
+
supportsRange: true;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
207
|
+
zstatic: {
|
|
208
|
+
id: string;
|
|
209
|
+
name: string;
|
|
210
|
+
baseUrl: string;
|
|
211
|
+
region: "china";
|
|
212
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
213
|
+
maxFileSize: number;
|
|
214
|
+
supportsRange: true;
|
|
215
|
+
description: string;
|
|
216
|
+
};
|
|
217
|
+
github_raw: {
|
|
218
|
+
id: string;
|
|
219
|
+
name: string;
|
|
220
|
+
baseUrl: string;
|
|
221
|
+
region: "global";
|
|
222
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
223
|
+
maxFileSize: number;
|
|
224
|
+
supportsRange: true;
|
|
225
|
+
description: string;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
declare const DEFAULT_GITHUB_CDN_NODES: CDNNode[];
|
|
229
|
+
declare function createWorkerNode(domain: string): CDNNode;
|
|
230
|
+
declare class CDNTester {
|
|
231
|
+
private timeout;
|
|
232
|
+
private retryCount;
|
|
233
|
+
constructor(timeout?: number, retryCount?: number);
|
|
234
|
+
testNode(node: CDNNode, testUrl?: string): Promise<LatencyResult>;
|
|
235
|
+
testAll(nodes: CDNNode[]): Promise<LatencyResult[]>;
|
|
236
|
+
testAllStreaming(nodes: CDNNode[], onResult: (r: LatencyResult) => void): Promise<LatencyResult[]>;
|
|
237
|
+
getBestNodeId(results: LatencyResult[]): string | null;
|
|
238
|
+
setTimeout(ms: number): void;
|
|
239
|
+
setRetryCount(n: number): void;
|
|
240
|
+
}
|
|
241
|
+
declare function getSortedNodesWithLatency(nodes: CDNNode[], results: Map<string, LatencyResult>): CDNNodeWithLatency[];
|
|
242
|
+
|
|
243
|
+
declare function parseInfoYaml(text: string): SplitInfo;
|
|
244
|
+
declare function serializeInfoYaml(info: SplitInfo): string;
|
|
245
|
+
declare function parseCacheYaml(text: string): SplitCache;
|
|
246
|
+
declare function serializeCacheYaml(cache: SplitCache): string;
|
|
247
|
+
|
|
248
|
+
declare class ForgeEngine {
|
|
249
|
+
private config;
|
|
250
|
+
private tester;
|
|
251
|
+
private fetcher;
|
|
252
|
+
private currentNodeId;
|
|
253
|
+
private latencyResults;
|
|
254
|
+
private initialized;
|
|
255
|
+
private readyResolve;
|
|
256
|
+
private readyPromise;
|
|
257
|
+
private splitInfoCache;
|
|
258
|
+
constructor(config: ForgeConfig);
|
|
259
|
+
private markReady;
|
|
260
|
+
waitReady(): Promise<void>;
|
|
261
|
+
initialize(): Promise<void>;
|
|
262
|
+
initializeStreaming(onResult: (r: LatencyResult) => void, onReady?: () => void): Promise<void>;
|
|
263
|
+
getNodes(): CDNNode[];
|
|
264
|
+
getCurrentNode(): CDNNode | null;
|
|
265
|
+
selectNode(nodeId: string): CDNNode | null;
|
|
266
|
+
getSortedNodes(): CDNNodeWithLatency[];
|
|
267
|
+
getLatencyResults(): Map<string, LatencyResult>;
|
|
268
|
+
testAndSelectBest(onResult?: (r: LatencyResult) => void): Promise<LatencyResult[]>;
|
|
269
|
+
testAllNodes(): Promise<LatencyResult[]>;
|
|
270
|
+
testAllNodesStreaming(onResult: (r: LatencyResult) => void): Promise<LatencyResult[]>;
|
|
271
|
+
buildUrl(filePath: string): string;
|
|
272
|
+
reqByCDN(filePath: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult>;
|
|
273
|
+
private tryGetSplitInfo;
|
|
274
|
+
private downloadSplit;
|
|
275
|
+
private downloadDirect;
|
|
276
|
+
private readStreamWithProgress;
|
|
277
|
+
private mapFilePath;
|
|
278
|
+
private loadSelectedNode;
|
|
279
|
+
private saveSelectedNode;
|
|
280
|
+
getConfig(): Required<ForgeConfig>;
|
|
281
|
+
isInitialized(): boolean;
|
|
282
|
+
clearSplitInfoCache(): void;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
interface ChunkedFetcherOptions {
|
|
286
|
+
maxConcurrency: number;
|
|
287
|
+
chunkTimeout: number;
|
|
288
|
+
maxRetries: number;
|
|
289
|
+
enableWorkStealing: boolean;
|
|
290
|
+
turboMode: boolean;
|
|
291
|
+
turboConcurrentCDNs: number;
|
|
292
|
+
}
|
|
293
|
+
interface ChunkedFetchResult {
|
|
294
|
+
blob: Blob;
|
|
295
|
+
totalSize: number;
|
|
296
|
+
totalTime: number;
|
|
297
|
+
nodeContributions: Map<string, {
|
|
298
|
+
bytes: number;
|
|
299
|
+
chunks: number;
|
|
300
|
+
avgSpeed: number;
|
|
301
|
+
}>;
|
|
302
|
+
usedParallelMode: boolean;
|
|
303
|
+
contentType: string;
|
|
304
|
+
}
|
|
305
|
+
declare class ChunkedFetcher {
|
|
306
|
+
private opts;
|
|
307
|
+
constructor(opts?: Partial<ChunkedFetcherOptions>);
|
|
308
|
+
downloadChunks(chunkUrls: string[][], chunkSizes: number[], totalSize: number, contentType: string, nodes: CDNNode[], latencyResults?: Map<string, LatencyResult>, onProgress?: (p: DownloadProgress) => void): Promise<ChunkedFetchResult>;
|
|
309
|
+
private downloadStandard;
|
|
310
|
+
private downloadTurbo;
|
|
311
|
+
private raceDownload;
|
|
312
|
+
private fetchChunk;
|
|
313
|
+
private buildResult;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
declare function CDNProvider({ config, children, onInitialized, onNodeChange, }: CDNProviderProps): React$1.ReactElement;
|
|
317
|
+
declare function useCDN(): CDNContextValue;
|
|
318
|
+
declare function useCDNUrl(relativePath: string): string;
|
|
319
|
+
declare function useCurrentCDNNode(): CDNNode | null;
|
|
320
|
+
declare function useCDNStatus(): CDNContextValue;
|
|
321
|
+
declare function useReqByCDN(): (filePath: string, onProgress?: (p: DownloadProgress) => void) => Promise<DownloadResult>;
|
|
322
|
+
|
|
323
|
+
declare const REGION_LABELS: Record<string, string>;
|
|
324
|
+
declare function getLatencyText(node: CDNNodeWithLatency): string;
|
|
325
|
+
declare function getLatencyClassName(node: CDNNodeWithLatency): string;
|
|
326
|
+
declare function CDNNodeSelector({ className, style, showLatency, showRegion, title, showRefreshButton, disabled, compact, onChange, onTestComplete, renderTrigger, renderNode, renderEmpty, renderLoading, }: CDNNodeSelectorProps): React$1.ReactElement;
|
|
327
|
+
|
|
328
|
+
export { type CDNContextValue, type CDNNode, CDNNodeSelector, type CDNNodeSelectorProps, type CDNNodeWithLatency, CDNProvider, type CDNProviderProps, type CDNRegion, CDNTester, CDN_NODE_PRESETS, type ChunkedFetchResult, ChunkedFetcher, type ChunkedFetcherOptions, DEFAULTS, DEFAULT_GITHUB_CDN_NODES, type DownloadProgress, type DownloadResult, type ForgeConfig, ForgeEngine, type GitHubContext, type LatencyResult, type LatencyStatus, type NodeRenderProps, REGION_LABELS, type SplitCache, type SplitChunkInfo, type SplitCommandOptions, type SplitInfo, createForgeConfig, createWorkerNode, getLatencyClassName, getLatencyText, getSortedNodesWithLatency, normalizeConfig, parseCacheYaml, parseInfoYaml, serializeCacheYaml, serializeInfoYaml, useCDN, useCDNStatus, useCDNUrl, useCurrentCDNNode, useReqByCDN };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import React$1 from 'react';
|
|
2
|
+
|
|
3
|
+
type CDNRegion = 'china' | 'asia' | 'global';
|
|
4
|
+
type LatencyStatus = 'idle' | 'testing' | 'success' | 'failed';
|
|
5
|
+
interface CDNNode {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
region: CDNRegion;
|
|
10
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
11
|
+
maxFileSize: number;
|
|
12
|
+
supportsRange: boolean;
|
|
13
|
+
testPath?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface GitHubContext {
|
|
18
|
+
user: string;
|
|
19
|
+
repo: string;
|
|
20
|
+
ref: string;
|
|
21
|
+
}
|
|
22
|
+
interface ForgeConfig {
|
|
23
|
+
github: GitHubContext;
|
|
24
|
+
nodes?: CDNNode[];
|
|
25
|
+
defaultNodeId?: string;
|
|
26
|
+
autoTest?: boolean;
|
|
27
|
+
testTimeout?: number;
|
|
28
|
+
testRetries?: number;
|
|
29
|
+
splitThreshold?: number;
|
|
30
|
+
mappingPrefix?: string;
|
|
31
|
+
splitStoragePath?: string;
|
|
32
|
+
storageKey?: string;
|
|
33
|
+
maxConcurrency?: number;
|
|
34
|
+
chunkTimeout?: number;
|
|
35
|
+
maxRetries?: number;
|
|
36
|
+
enableWorkStealing?: boolean;
|
|
37
|
+
turboMode?: boolean;
|
|
38
|
+
turboConcurrentCDNs?: number;
|
|
39
|
+
}
|
|
40
|
+
interface SplitInfo {
|
|
41
|
+
originalName: string;
|
|
42
|
+
totalSize: number;
|
|
43
|
+
mimeType: string;
|
|
44
|
+
chunks: SplitChunkInfo[];
|
|
45
|
+
createdAt: string;
|
|
46
|
+
chunkSize: number;
|
|
47
|
+
}
|
|
48
|
+
interface SplitChunkInfo {
|
|
49
|
+
fileName: string;
|
|
50
|
+
index: number;
|
|
51
|
+
size: number;
|
|
52
|
+
sha256: string;
|
|
53
|
+
}
|
|
54
|
+
interface SplitCache {
|
|
55
|
+
sourcePath: string;
|
|
56
|
+
sourceHash: string;
|
|
57
|
+
sourceSize: number;
|
|
58
|
+
generatedAt: string;
|
|
59
|
+
}
|
|
60
|
+
interface LatencyResult {
|
|
61
|
+
nodeId: string;
|
|
62
|
+
latency: number;
|
|
63
|
+
success: boolean;
|
|
64
|
+
timestamp: number;
|
|
65
|
+
error?: string;
|
|
66
|
+
}
|
|
67
|
+
interface CDNNodeWithLatency extends CDNNode {
|
|
68
|
+
latency?: number;
|
|
69
|
+
latencyStatus?: LatencyStatus;
|
|
70
|
+
}
|
|
71
|
+
interface DownloadProgress {
|
|
72
|
+
loaded: number;
|
|
73
|
+
total: number;
|
|
74
|
+
percentage: number;
|
|
75
|
+
speed: number;
|
|
76
|
+
eta: number;
|
|
77
|
+
completedChunks: number;
|
|
78
|
+
totalChunks: number;
|
|
79
|
+
}
|
|
80
|
+
interface DownloadResult {
|
|
81
|
+
blob: Blob;
|
|
82
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
83
|
+
totalSize: number;
|
|
84
|
+
totalTime: number;
|
|
85
|
+
contentType: string;
|
|
86
|
+
usedSplitMode: boolean;
|
|
87
|
+
usedParallelMode: boolean;
|
|
88
|
+
nodeContributions: Map<string, {
|
|
89
|
+
bytes: number;
|
|
90
|
+
chunks: number;
|
|
91
|
+
avgSpeed: number;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
interface CDNProviderProps {
|
|
95
|
+
config: ForgeConfig;
|
|
96
|
+
children: React.ReactNode;
|
|
97
|
+
onInitialized?: (currentNode: CDNNode | null) => void;
|
|
98
|
+
onNodeChange?: (node: CDNNode) => void;
|
|
99
|
+
}
|
|
100
|
+
interface CDNContextValue {
|
|
101
|
+
config: ForgeConfig;
|
|
102
|
+
currentNode: CDNNode | null;
|
|
103
|
+
nodes: CDNNodeWithLatency[];
|
|
104
|
+
isTesting: boolean;
|
|
105
|
+
isInitialized: boolean;
|
|
106
|
+
latencyResults: Map<string, LatencyResult>;
|
|
107
|
+
selectNode: (nodeId: string) => void;
|
|
108
|
+
testAllNodes: () => Promise<LatencyResult[]>;
|
|
109
|
+
reqByCDN: (filePath: string, onProgress?: (p: DownloadProgress) => void) => Promise<DownloadResult>;
|
|
110
|
+
buildUrl: (filePath: string) => string;
|
|
111
|
+
getSortedNodes: () => CDNNodeWithLatency[];
|
|
112
|
+
}
|
|
113
|
+
interface CDNNodeSelectorProps {
|
|
114
|
+
className?: string;
|
|
115
|
+
style?: React.CSSProperties;
|
|
116
|
+
showLatency?: boolean;
|
|
117
|
+
showRegion?: boolean;
|
|
118
|
+
title?: string;
|
|
119
|
+
showRefreshButton?: boolean;
|
|
120
|
+
disabled?: boolean;
|
|
121
|
+
compact?: boolean;
|
|
122
|
+
onChange?: (node: CDNNode) => void;
|
|
123
|
+
onTestComplete?: (results: LatencyResult[]) => void;
|
|
124
|
+
renderTrigger?: (props: {
|
|
125
|
+
currentNode: CDNNode | null;
|
|
126
|
+
isOpen: boolean;
|
|
127
|
+
isTesting: boolean;
|
|
128
|
+
}) => React.ReactNode;
|
|
129
|
+
renderNode?: (props: NodeRenderProps) => React.ReactNode;
|
|
130
|
+
renderEmpty?: () => React.ReactNode;
|
|
131
|
+
renderLoading?: () => React.ReactNode;
|
|
132
|
+
}
|
|
133
|
+
interface NodeRenderProps {
|
|
134
|
+
node: CDNNodeWithLatency;
|
|
135
|
+
isSelected: boolean;
|
|
136
|
+
isDisabled: boolean;
|
|
137
|
+
latencyText: string;
|
|
138
|
+
latencyClassName: string;
|
|
139
|
+
onSelect: () => void;
|
|
140
|
+
}
|
|
141
|
+
interface SplitCommandOptions {
|
|
142
|
+
source: string;
|
|
143
|
+
outputDir: string;
|
|
144
|
+
mappingPrefix: string;
|
|
145
|
+
chunkSize?: number;
|
|
146
|
+
force?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
declare const DEFAULTS: {
|
|
150
|
+
readonly splitThreshold: number;
|
|
151
|
+
readonly chunkSizeForSplit: number;
|
|
152
|
+
readonly testTimeout: 5000;
|
|
153
|
+
readonly testRetries: 2;
|
|
154
|
+
readonly maxConcurrency: 6;
|
|
155
|
+
readonly chunkTimeout: 30000;
|
|
156
|
+
readonly maxRetries: 3;
|
|
157
|
+
readonly enableWorkStealing: true;
|
|
158
|
+
readonly turboMode: false;
|
|
159
|
+
readonly turboConcurrentCDNs: 3;
|
|
160
|
+
readonly storageKey: "hx-cdn-forge-node";
|
|
161
|
+
readonly autoTest: true;
|
|
162
|
+
};
|
|
163
|
+
declare function normalizeConfig(config: ForgeConfig): Required<ForgeConfig>;
|
|
164
|
+
declare function createForgeConfig(github: GitHubContext, options?: Partial<Omit<ForgeConfig, 'github'>>): ForgeConfig;
|
|
165
|
+
|
|
166
|
+
declare const CDN_NODE_PRESETS: {
|
|
167
|
+
jsdelivr_main: {
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
baseUrl: string;
|
|
171
|
+
region: "global";
|
|
172
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
173
|
+
maxFileSize: number;
|
|
174
|
+
supportsRange: true;
|
|
175
|
+
description: string;
|
|
176
|
+
};
|
|
177
|
+
jsdelivr_fastly: {
|
|
178
|
+
id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
baseUrl: string;
|
|
181
|
+
region: "global";
|
|
182
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
183
|
+
maxFileSize: number;
|
|
184
|
+
supportsRange: true;
|
|
185
|
+
description: string;
|
|
186
|
+
};
|
|
187
|
+
jsdelivr_testing: {
|
|
188
|
+
id: string;
|
|
189
|
+
name: string;
|
|
190
|
+
baseUrl: string;
|
|
191
|
+
region: "global";
|
|
192
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
193
|
+
maxFileSize: number;
|
|
194
|
+
supportsRange: true;
|
|
195
|
+
description: string;
|
|
196
|
+
};
|
|
197
|
+
jsd_mirror: {
|
|
198
|
+
id: string;
|
|
199
|
+
name: string;
|
|
200
|
+
baseUrl: string;
|
|
201
|
+
region: "china";
|
|
202
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
203
|
+
maxFileSize: number;
|
|
204
|
+
supportsRange: true;
|
|
205
|
+
description: string;
|
|
206
|
+
};
|
|
207
|
+
zstatic: {
|
|
208
|
+
id: string;
|
|
209
|
+
name: string;
|
|
210
|
+
baseUrl: string;
|
|
211
|
+
region: "china";
|
|
212
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
213
|
+
maxFileSize: number;
|
|
214
|
+
supportsRange: true;
|
|
215
|
+
description: string;
|
|
216
|
+
};
|
|
217
|
+
github_raw: {
|
|
218
|
+
id: string;
|
|
219
|
+
name: string;
|
|
220
|
+
baseUrl: string;
|
|
221
|
+
region: "global";
|
|
222
|
+
buildUrl: (ctx: GitHubContext, filePath: string) => string;
|
|
223
|
+
maxFileSize: number;
|
|
224
|
+
supportsRange: true;
|
|
225
|
+
description: string;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
declare const DEFAULT_GITHUB_CDN_NODES: CDNNode[];
|
|
229
|
+
declare function createWorkerNode(domain: string): CDNNode;
|
|
230
|
+
declare class CDNTester {
|
|
231
|
+
private timeout;
|
|
232
|
+
private retryCount;
|
|
233
|
+
constructor(timeout?: number, retryCount?: number);
|
|
234
|
+
testNode(node: CDNNode, testUrl?: string): Promise<LatencyResult>;
|
|
235
|
+
testAll(nodes: CDNNode[]): Promise<LatencyResult[]>;
|
|
236
|
+
testAllStreaming(nodes: CDNNode[], onResult: (r: LatencyResult) => void): Promise<LatencyResult[]>;
|
|
237
|
+
getBestNodeId(results: LatencyResult[]): string | null;
|
|
238
|
+
setTimeout(ms: number): void;
|
|
239
|
+
setRetryCount(n: number): void;
|
|
240
|
+
}
|
|
241
|
+
declare function getSortedNodesWithLatency(nodes: CDNNode[], results: Map<string, LatencyResult>): CDNNodeWithLatency[];
|
|
242
|
+
|
|
243
|
+
declare function parseInfoYaml(text: string): SplitInfo;
|
|
244
|
+
declare function serializeInfoYaml(info: SplitInfo): string;
|
|
245
|
+
declare function parseCacheYaml(text: string): SplitCache;
|
|
246
|
+
declare function serializeCacheYaml(cache: SplitCache): string;
|
|
247
|
+
|
|
248
|
+
declare class ForgeEngine {
|
|
249
|
+
private config;
|
|
250
|
+
private tester;
|
|
251
|
+
private fetcher;
|
|
252
|
+
private currentNodeId;
|
|
253
|
+
private latencyResults;
|
|
254
|
+
private initialized;
|
|
255
|
+
private readyResolve;
|
|
256
|
+
private readyPromise;
|
|
257
|
+
private splitInfoCache;
|
|
258
|
+
constructor(config: ForgeConfig);
|
|
259
|
+
private markReady;
|
|
260
|
+
waitReady(): Promise<void>;
|
|
261
|
+
initialize(): Promise<void>;
|
|
262
|
+
initializeStreaming(onResult: (r: LatencyResult) => void, onReady?: () => void): Promise<void>;
|
|
263
|
+
getNodes(): CDNNode[];
|
|
264
|
+
getCurrentNode(): CDNNode | null;
|
|
265
|
+
selectNode(nodeId: string): CDNNode | null;
|
|
266
|
+
getSortedNodes(): CDNNodeWithLatency[];
|
|
267
|
+
getLatencyResults(): Map<string, LatencyResult>;
|
|
268
|
+
testAndSelectBest(onResult?: (r: LatencyResult) => void): Promise<LatencyResult[]>;
|
|
269
|
+
testAllNodes(): Promise<LatencyResult[]>;
|
|
270
|
+
testAllNodesStreaming(onResult: (r: LatencyResult) => void): Promise<LatencyResult[]>;
|
|
271
|
+
buildUrl(filePath: string): string;
|
|
272
|
+
reqByCDN(filePath: string, onProgress?: (p: DownloadProgress) => void): Promise<DownloadResult>;
|
|
273
|
+
private tryGetSplitInfo;
|
|
274
|
+
private downloadSplit;
|
|
275
|
+
private downloadDirect;
|
|
276
|
+
private readStreamWithProgress;
|
|
277
|
+
private mapFilePath;
|
|
278
|
+
private loadSelectedNode;
|
|
279
|
+
private saveSelectedNode;
|
|
280
|
+
getConfig(): Required<ForgeConfig>;
|
|
281
|
+
isInitialized(): boolean;
|
|
282
|
+
clearSplitInfoCache(): void;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
interface ChunkedFetcherOptions {
|
|
286
|
+
maxConcurrency: number;
|
|
287
|
+
chunkTimeout: number;
|
|
288
|
+
maxRetries: number;
|
|
289
|
+
enableWorkStealing: boolean;
|
|
290
|
+
turboMode: boolean;
|
|
291
|
+
turboConcurrentCDNs: number;
|
|
292
|
+
}
|
|
293
|
+
interface ChunkedFetchResult {
|
|
294
|
+
blob: Blob;
|
|
295
|
+
totalSize: number;
|
|
296
|
+
totalTime: number;
|
|
297
|
+
nodeContributions: Map<string, {
|
|
298
|
+
bytes: number;
|
|
299
|
+
chunks: number;
|
|
300
|
+
avgSpeed: number;
|
|
301
|
+
}>;
|
|
302
|
+
usedParallelMode: boolean;
|
|
303
|
+
contentType: string;
|
|
304
|
+
}
|
|
305
|
+
declare class ChunkedFetcher {
|
|
306
|
+
private opts;
|
|
307
|
+
constructor(opts?: Partial<ChunkedFetcherOptions>);
|
|
308
|
+
downloadChunks(chunkUrls: string[][], chunkSizes: number[], totalSize: number, contentType: string, nodes: CDNNode[], latencyResults?: Map<string, LatencyResult>, onProgress?: (p: DownloadProgress) => void): Promise<ChunkedFetchResult>;
|
|
309
|
+
private downloadStandard;
|
|
310
|
+
private downloadTurbo;
|
|
311
|
+
private raceDownload;
|
|
312
|
+
private fetchChunk;
|
|
313
|
+
private buildResult;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
declare function CDNProvider({ config, children, onInitialized, onNodeChange, }: CDNProviderProps): React$1.ReactElement;
|
|
317
|
+
declare function useCDN(): CDNContextValue;
|
|
318
|
+
declare function useCDNUrl(relativePath: string): string;
|
|
319
|
+
declare function useCurrentCDNNode(): CDNNode | null;
|
|
320
|
+
declare function useCDNStatus(): CDNContextValue;
|
|
321
|
+
declare function useReqByCDN(): (filePath: string, onProgress?: (p: DownloadProgress) => void) => Promise<DownloadResult>;
|
|
322
|
+
|
|
323
|
+
declare const REGION_LABELS: Record<string, string>;
|
|
324
|
+
declare function getLatencyText(node: CDNNodeWithLatency): string;
|
|
325
|
+
declare function getLatencyClassName(node: CDNNodeWithLatency): string;
|
|
326
|
+
declare function CDNNodeSelector({ className, style, showLatency, showRegion, title, showRefreshButton, disabled, compact, onChange, onTestComplete, renderTrigger, renderNode, renderEmpty, renderLoading, }: CDNNodeSelectorProps): React$1.ReactElement;
|
|
327
|
+
|
|
328
|
+
export { type CDNContextValue, type CDNNode, CDNNodeSelector, type CDNNodeSelectorProps, type CDNNodeWithLatency, CDNProvider, type CDNProviderProps, type CDNRegion, CDNTester, CDN_NODE_PRESETS, type ChunkedFetchResult, ChunkedFetcher, type ChunkedFetcherOptions, DEFAULTS, DEFAULT_GITHUB_CDN_NODES, type DownloadProgress, type DownloadResult, type ForgeConfig, ForgeEngine, type GitHubContext, type LatencyResult, type LatencyStatus, type NodeRenderProps, REGION_LABELS, type SplitCache, type SplitChunkInfo, type SplitCommandOptions, type SplitInfo, createForgeConfig, createWorkerNode, getLatencyClassName, getLatencyText, getSortedNodesWithLatency, normalizeConfig, parseCacheYaml, parseInfoYaml, serializeCacheYaml, serializeInfoYaml, useCDN, useCDNStatus, useCDNUrl, useCurrentCDNNode, useReqByCDN };
|