recker 1.0.2-0 → 1.0.3
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 +0 -2
- package/README.md +121 -72
- package/dist/cache/memory-storage.d.ts.map +1 -1
- package/dist/cache/memory-storage.js +7 -1
- package/dist/constants/http-status.d.ts +74 -0
- package/dist/constants/http-status.d.ts.map +1 -0
- package/dist/constants/http-status.js +156 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +6 -6
- package/dist/cookies/memory-cookie-jar.d.ts +31 -0
- package/dist/cookies/memory-cookie-jar.d.ts.map +1 -0
- package/dist/cookies/memory-cookie-jar.js +210 -0
- package/dist/core/client.d.ts +9 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +252 -53
- package/dist/core/errors.d.ts +18 -2
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +66 -5
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +5 -0
- package/dist/core/request-promise.d.ts.map +1 -1
- package/dist/core/request-promise.js +8 -2
- package/dist/core/request.d.ts +7 -1
- package/dist/core/request.d.ts.map +1 -1
- package/dist/core/request.js +32 -0
- package/dist/core/response.d.ts +2 -0
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +44 -19
- package/dist/events/request-events.d.ts +48 -0
- package/dist/events/request-events.d.ts.map +1 -0
- package/dist/events/request-events.js +85 -0
- package/dist/index.d.ts +28 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -2
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +16 -5
- package/dist/mcp/contract.d.ts +77 -0
- package/dist/mcp/contract.d.ts.map +1 -0
- package/dist/mcp/contract.js +278 -0
- package/dist/mcp/types.d.ts +1 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/plugins/auth.d.ts +45 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +268 -0
- package/dist/plugins/cache.d.ts +7 -1
- package/dist/plugins/cache.d.ts.map +1 -1
- package/dist/plugins/cache.js +470 -49
- package/dist/plugins/circuit-breaker.js +1 -1
- package/dist/plugins/compression.d.ts.map +1 -1
- package/dist/plugins/compression.js +3 -3
- package/dist/plugins/dedup.d.ts.map +1 -1
- package/dist/plugins/dedup.js +2 -1
- package/dist/plugins/graphql.d.ts +4 -3
- package/dist/plugins/graphql.d.ts.map +1 -1
- package/dist/plugins/graphql.js +24 -5
- package/dist/plugins/grpc-web.d.ts +80 -0
- package/dist/plugins/grpc-web.d.ts.map +1 -0
- package/dist/plugins/grpc-web.js +261 -0
- package/dist/plugins/har-player.d.ts.map +1 -1
- package/dist/plugins/har-player.js +11 -2
- package/dist/plugins/hls.d.ts +33 -0
- package/dist/plugins/hls.d.ts.map +1 -0
- package/dist/plugins/hls.js +225 -0
- package/dist/plugins/http2-push.d.ts +64 -0
- package/dist/plugins/http2-push.d.ts.map +1 -0
- package/dist/plugins/http2-push.js +274 -0
- package/dist/plugins/http3.d.ts +76 -0
- package/dist/plugins/http3.d.ts.map +1 -0
- package/dist/plugins/http3.js +231 -0
- package/dist/plugins/interface-rotator.d.ts +10 -0
- package/dist/plugins/interface-rotator.d.ts.map +1 -0
- package/dist/plugins/interface-rotator.js +57 -0
- package/dist/plugins/jsonrpc.d.ts +76 -0
- package/dist/plugins/jsonrpc.d.ts.map +1 -0
- package/dist/plugins/jsonrpc.js +143 -0
- package/dist/plugins/logger.d.ts +8 -5
- package/dist/plugins/logger.d.ts.map +1 -1
- package/dist/plugins/logger.js +66 -30
- package/dist/plugins/odata.d.ts +182 -0
- package/dist/plugins/odata.d.ts.map +1 -0
- package/dist/plugins/odata.js +561 -0
- package/dist/plugins/retry.d.ts +1 -0
- package/dist/plugins/retry.d.ts.map +1 -1
- package/dist/plugins/retry.js +26 -2
- package/dist/plugins/scrape.d.ts +22 -0
- package/dist/plugins/scrape.d.ts.map +1 -0
- package/dist/plugins/scrape.js +87 -0
- package/dist/plugins/soap.d.ts +73 -0
- package/dist/plugins/soap.d.ts.map +1 -0
- package/dist/plugins/soap.js +347 -0
- package/dist/plugins/user-agent.d.ts +8 -0
- package/dist/plugins/user-agent.d.ts.map +1 -0
- package/dist/plugins/user-agent.js +46 -0
- package/dist/plugins/xml.d.ts +10 -0
- package/dist/plugins/xml.d.ts.map +1 -0
- package/dist/plugins/xml.js +194 -0
- package/dist/presets/anthropic.d.ts +7 -0
- package/dist/presets/anthropic.d.ts.map +1 -0
- package/dist/presets/anthropic.js +17 -0
- package/dist/presets/azure-openai.d.ts +9 -0
- package/dist/presets/azure-openai.d.ts.map +1 -0
- package/dist/presets/azure-openai.js +25 -0
- package/dist/presets/cloudflare.d.ts +13 -0
- package/dist/presets/cloudflare.d.ts.map +1 -0
- package/dist/presets/cloudflare.js +39 -0
- package/dist/presets/cohere.d.ts +6 -0
- package/dist/presets/cohere.d.ts.map +1 -0
- package/dist/presets/cohere.js +16 -0
- package/dist/presets/deepseek.d.ts +6 -0
- package/dist/presets/deepseek.d.ts.map +1 -0
- package/dist/presets/deepseek.js +16 -0
- package/dist/presets/digitalocean.d.ts +6 -0
- package/dist/presets/digitalocean.d.ts.map +1 -0
- package/dist/presets/digitalocean.js +16 -0
- package/dist/presets/discord.d.ts +7 -0
- package/dist/presets/discord.d.ts.map +1 -0
- package/dist/presets/discord.js +17 -0
- package/dist/presets/fireworks.d.ts +6 -0
- package/dist/presets/fireworks.d.ts.map +1 -0
- package/dist/presets/fireworks.js +16 -0
- package/dist/presets/gemini.d.ts +6 -0
- package/dist/presets/gemini.d.ts.map +1 -0
- package/dist/presets/gemini.js +16 -0
- package/dist/presets/github.d.ts +7 -0
- package/dist/presets/github.d.ts.map +1 -0
- package/dist/presets/github.js +17 -0
- package/dist/presets/gitlab.d.ts +7 -0
- package/dist/presets/gitlab.d.ts.map +1 -0
- package/dist/presets/gitlab.js +16 -0
- package/dist/presets/groq.d.ts +6 -0
- package/dist/presets/groq.d.ts.map +1 -0
- package/dist/presets/groq.js +16 -0
- package/dist/presets/huggingface.d.ts +6 -0
- package/dist/presets/huggingface.d.ts.map +1 -0
- package/dist/presets/huggingface.js +16 -0
- package/dist/presets/index.d.ts +28 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +27 -0
- package/dist/presets/linear.d.ts +6 -0
- package/dist/presets/linear.d.ts.map +1 -0
- package/dist/presets/linear.js +16 -0
- package/dist/presets/mistral.d.ts +6 -0
- package/dist/presets/mistral.d.ts.map +1 -0
- package/dist/presets/mistral.js +16 -0
- package/dist/presets/notion.d.ts +7 -0
- package/dist/presets/notion.d.ts.map +1 -0
- package/dist/presets/notion.js +17 -0
- package/dist/presets/openai.d.ts +8 -0
- package/dist/presets/openai.d.ts.map +1 -0
- package/dist/presets/openai.js +23 -0
- package/dist/presets/perplexity.d.ts +6 -0
- package/dist/presets/perplexity.d.ts.map +1 -0
- package/dist/presets/perplexity.js +16 -0
- package/dist/presets/registry.d.ts +20 -0
- package/dist/presets/registry.d.ts.map +1 -0
- package/dist/presets/registry.js +311 -0
- package/dist/presets/replicate.d.ts +6 -0
- package/dist/presets/replicate.d.ts.map +1 -0
- package/dist/presets/replicate.js +16 -0
- package/dist/presets/slack.d.ts +6 -0
- package/dist/presets/slack.d.ts.map +1 -0
- package/dist/presets/slack.js +16 -0
- package/dist/presets/stripe.d.ts +8 -0
- package/dist/presets/stripe.d.ts.map +1 -0
- package/dist/presets/stripe.js +23 -0
- package/dist/presets/supabase.d.ts +7 -0
- package/dist/presets/supabase.d.ts.map +1 -0
- package/dist/presets/supabase.js +18 -0
- package/dist/presets/together.d.ts +6 -0
- package/dist/presets/together.d.ts.map +1 -0
- package/dist/presets/together.js +16 -0
- package/dist/presets/twilio.d.ts +7 -0
- package/dist/presets/twilio.d.ts.map +1 -0
- package/dist/presets/twilio.js +17 -0
- package/dist/presets/vercel.d.ts +7 -0
- package/dist/presets/vercel.d.ts.map +1 -0
- package/dist/presets/vercel.js +23 -0
- package/dist/presets/xai.d.ts +7 -0
- package/dist/presets/xai.d.ts.map +1 -0
- package/dist/presets/xai.js +17 -0
- package/dist/protocols/ftp.d.ts +63 -0
- package/dist/protocols/ftp.d.ts.map +1 -0
- package/dist/protocols/ftp.js +388 -0
- package/dist/protocols/index.d.ts +4 -0
- package/dist/protocols/index.d.ts.map +1 -0
- package/dist/protocols/index.js +3 -0
- package/dist/protocols/sftp.d.ts +65 -0
- package/dist/protocols/sftp.d.ts.map +1 -0
- package/dist/protocols/sftp.js +346 -0
- package/dist/protocols/telnet.d.ts +50 -0
- package/dist/protocols/telnet.d.ts.map +1 -0
- package/dist/protocols/telnet.js +139 -0
- package/dist/runner/request-runner.d.ts.map +1 -1
- package/dist/runner/request-runner.js +1 -0
- package/dist/scrape/document.d.ts +44 -0
- package/dist/scrape/document.d.ts.map +1 -0
- package/dist/scrape/document.js +198 -0
- package/dist/scrape/element.d.ts +50 -0
- package/dist/scrape/element.d.ts.map +1 -0
- package/dist/scrape/element.js +176 -0
- package/dist/scrape/extractors.d.ts +17 -0
- package/dist/scrape/extractors.d.ts.map +1 -0
- package/dist/scrape/extractors.js +356 -0
- package/dist/scrape/index.d.ts +5 -0
- package/dist/scrape/index.d.ts.map +1 -0
- package/dist/scrape/index.js +3 -0
- package/dist/scrape/types.d.ts +108 -0
- package/dist/scrape/types.d.ts.map +1 -0
- package/dist/scrape/types.js +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/mock.d.ts +58 -0
- package/dist/testing/mock.d.ts.map +1 -0
- package/dist/testing/mock.js +252 -0
- package/dist/transport/fetch.d.ts.map +1 -1
- package/dist/transport/fetch.js +12 -4
- package/dist/transport/undici.d.ts +17 -1
- package/dist/transport/undici.d.ts.map +1 -1
- package/dist/transport/undici.js +708 -47
- package/dist/types/index.d.ts +111 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/logger.d.ts +17 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logger.js +66 -0
- package/dist/utils/agent-manager.d.ts.map +1 -1
- package/dist/utils/agent-manager.js +20 -4
- package/dist/utils/body.d.ts.map +1 -1
- package/dist/utils/body.js +14 -2
- package/dist/utils/charset.d.ts +16 -0
- package/dist/utils/charset.d.ts.map +1 -0
- package/dist/utils/charset.js +169 -0
- package/dist/utils/client-pool.d.ts +21 -0
- package/dist/utils/client-pool.d.ts.map +1 -0
- package/dist/utils/client-pool.js +49 -0
- package/dist/utils/concurrency.d.ts.map +1 -1
- package/dist/utils/concurrency.js +8 -4
- package/dist/utils/dns-toolkit.d.ts +13 -0
- package/dist/utils/dns-toolkit.d.ts.map +1 -0
- package/dist/utils/dns-toolkit.js +48 -0
- package/dist/utils/doh.d.ts.map +1 -1
- package/dist/utils/doh.js +16 -3
- package/dist/utils/download.d.ts +15 -0
- package/dist/utils/download.d.ts.map +1 -0
- package/dist/utils/download.js +44 -0
- package/dist/utils/env-proxy.d.ts +13 -0
- package/dist/utils/env-proxy.d.ts.map +1 -0
- package/dist/utils/env-proxy.js +105 -0
- package/dist/utils/header-parser.d.ts +15 -1
- package/dist/utils/header-parser.d.ts.map +1 -1
- package/dist/utils/header-parser.js +161 -1
- package/dist/utils/link-header.d.ts +70 -0
- package/dist/utils/link-header.d.ts.map +1 -0
- package/dist/utils/link-header.js +190 -0
- package/dist/utils/progress.d.ts +7 -2
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +48 -15
- package/dist/utils/rdap.d.ts +17 -0
- package/dist/utils/rdap.d.ts.map +1 -0
- package/dist/utils/rdap.js +32 -0
- package/dist/utils/request-pool.d.ts.map +1 -1
- package/dist/utils/request-pool.js +4 -3
- package/dist/utils/sse.d.ts.map +1 -1
- package/dist/utils/sse.js +8 -2
- package/dist/utils/status-codes.d.ts +84 -0
- package/dist/utils/status-codes.d.ts.map +1 -0
- package/dist/utils/status-codes.js +204 -0
- package/dist/utils/streaming.d.ts.map +1 -1
- package/dist/utils/streaming.js +1 -0
- package/dist/utils/tls-inspector.d.ts +21 -0
- package/dist/utils/tls-inspector.d.ts.map +1 -0
- package/dist/utils/tls-inspector.js +39 -0
- package/dist/utils/try-fn.d.ts.map +1 -1
- package/dist/utils/try-fn.js +11 -5
- package/dist/utils/upload.d.ts +1 -0
- package/dist/utils/upload.d.ts.map +1 -1
- package/dist/utils/upload.js +20 -3
- package/dist/utils/user-agent.d.ts +9 -9
- package/dist/utils/user-agent.js +9 -9
- package/dist/utils/whois.d.ts.map +1 -1
- package/dist/utils/whois.js +11 -2
- package/dist/websocket/client.d.ts +29 -1
- package/dist/websocket/client.d.ts.map +1 -1
- package/dist/websocket/client.js +145 -13
- package/package.json +45 -8
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Client } from '../core/client.js';
|
|
2
|
+
export interface GrpcWebOptions {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
metadata?: Record<string, string>;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
textFormat?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface GrpcMetadata {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GrpcCallOptions {
|
|
12
|
+
metadata?: GrpcMetadata;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}
|
|
16
|
+
export interface GrpcStatus {
|
|
17
|
+
code: number;
|
|
18
|
+
message: string;
|
|
19
|
+
details?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface GrpcResponse<T> {
|
|
22
|
+
message: T;
|
|
23
|
+
metadata: GrpcMetadata;
|
|
24
|
+
status: GrpcStatus;
|
|
25
|
+
}
|
|
26
|
+
export interface UnaryCall<TRequest, TResponse> {
|
|
27
|
+
(request: TRequest, options?: GrpcCallOptions): Promise<GrpcResponse<TResponse>>;
|
|
28
|
+
}
|
|
29
|
+
export interface ServerStreamCall<TRequest, TResponse> {
|
|
30
|
+
(request: TRequest, options?: GrpcCallOptions): AsyncGenerator<TResponse, void, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export declare const GrpcStatusCode: {
|
|
33
|
+
readonly OK: 0;
|
|
34
|
+
readonly CANCELLED: 1;
|
|
35
|
+
readonly UNKNOWN: 2;
|
|
36
|
+
readonly INVALID_ARGUMENT: 3;
|
|
37
|
+
readonly DEADLINE_EXCEEDED: 4;
|
|
38
|
+
readonly NOT_FOUND: 5;
|
|
39
|
+
readonly ALREADY_EXISTS: 6;
|
|
40
|
+
readonly PERMISSION_DENIED: 7;
|
|
41
|
+
readonly RESOURCE_EXHAUSTED: 8;
|
|
42
|
+
readonly FAILED_PRECONDITION: 9;
|
|
43
|
+
readonly ABORTED: 10;
|
|
44
|
+
readonly OUT_OF_RANGE: 11;
|
|
45
|
+
readonly UNIMPLEMENTED: 12;
|
|
46
|
+
readonly INTERNAL: 13;
|
|
47
|
+
readonly UNAVAILABLE: 14;
|
|
48
|
+
readonly DATA_LOSS: 15;
|
|
49
|
+
readonly UNAUTHENTICATED: 16;
|
|
50
|
+
};
|
|
51
|
+
export type GrpcStatusCodeType = typeof GrpcStatusCode[keyof typeof GrpcStatusCode];
|
|
52
|
+
export declare class GrpcError extends Error {
|
|
53
|
+
readonly code: GrpcStatusCodeType;
|
|
54
|
+
readonly metadata: GrpcMetadata;
|
|
55
|
+
readonly details?: unknown;
|
|
56
|
+
constructor(status: GrpcStatus, metadata?: GrpcMetadata);
|
|
57
|
+
static fromCode(code: GrpcStatusCodeType, message: string): GrpcError;
|
|
58
|
+
}
|
|
59
|
+
export interface MessageCodec<T> {
|
|
60
|
+
encode(message: T): Uint8Array;
|
|
61
|
+
decode(data: Uint8Array): T;
|
|
62
|
+
}
|
|
63
|
+
export declare function jsonCodec<T>(): MessageCodec<T>;
|
|
64
|
+
export declare class GrpcWebClient {
|
|
65
|
+
private client;
|
|
66
|
+
private options;
|
|
67
|
+
constructor(client: Client, options: GrpcWebOptions);
|
|
68
|
+
unary<TRequest, TResponse>(service: string, method: string, request: TRequest, codec: MessageCodec<TRequest> & MessageCodec<TResponse>, options?: GrpcCallOptions): Promise<GrpcResponse<TResponse>>;
|
|
69
|
+
serverStream<TRequest, TResponse>(service: string, method: string, request: TRequest, codec: MessageCodec<TRequest> & MessageCodec<TResponse>, options?: GrpcCallOptions): AsyncGenerator<TResponse, void, unknown>;
|
|
70
|
+
service<T extends Record<string, unknown>>(serviceName: string, methods: T): T;
|
|
71
|
+
private parseUnaryResponse;
|
|
72
|
+
}
|
|
73
|
+
export declare function createGrpcWebClient(client: Client, options: GrpcWebOptions): GrpcWebClient;
|
|
74
|
+
export declare function grpcWeb(): (client: Client) => void;
|
|
75
|
+
declare module '../core/client.js' {
|
|
76
|
+
interface Client {
|
|
77
|
+
grpcWeb(options?: Partial<GrpcWebOptions>): GrpcWebClient;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=grpc-web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc-web.d.ts","sourceRoot":"","sources":["../../src/plugins/grpc-web.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export const GrpcStatusCode = {
|
|
2
|
+
OK: 0,
|
|
3
|
+
CANCELLED: 1,
|
|
4
|
+
UNKNOWN: 2,
|
|
5
|
+
INVALID_ARGUMENT: 3,
|
|
6
|
+
DEADLINE_EXCEEDED: 4,
|
|
7
|
+
NOT_FOUND: 5,
|
|
8
|
+
ALREADY_EXISTS: 6,
|
|
9
|
+
PERMISSION_DENIED: 7,
|
|
10
|
+
RESOURCE_EXHAUSTED: 8,
|
|
11
|
+
FAILED_PRECONDITION: 9,
|
|
12
|
+
ABORTED: 10,
|
|
13
|
+
OUT_OF_RANGE: 11,
|
|
14
|
+
UNIMPLEMENTED: 12,
|
|
15
|
+
INTERNAL: 13,
|
|
16
|
+
UNAVAILABLE: 14,
|
|
17
|
+
DATA_LOSS: 15,
|
|
18
|
+
UNAUTHENTICATED: 16,
|
|
19
|
+
};
|
|
20
|
+
export class GrpcError extends Error {
|
|
21
|
+
code;
|
|
22
|
+
metadata;
|
|
23
|
+
details;
|
|
24
|
+
constructor(status, metadata = {}) {
|
|
25
|
+
super(status.message);
|
|
26
|
+
this.name = 'GrpcError';
|
|
27
|
+
this.code = status.code;
|
|
28
|
+
this.metadata = metadata;
|
|
29
|
+
this.details = status.details;
|
|
30
|
+
}
|
|
31
|
+
static fromCode(code, message) {
|
|
32
|
+
return new GrpcError({ code, message }, {});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function jsonCodec() {
|
|
36
|
+
const encoder = new TextEncoder();
|
|
37
|
+
const decoder = new TextDecoder();
|
|
38
|
+
return {
|
|
39
|
+
encode(message) {
|
|
40
|
+
return encoder.encode(JSON.stringify(message));
|
|
41
|
+
},
|
|
42
|
+
decode(data) {
|
|
43
|
+
return JSON.parse(decoder.decode(data));
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function encodeGrpcFrame(data, isTrailers = false) {
|
|
48
|
+
const frame = new Uint8Array(5 + data.length);
|
|
49
|
+
frame[0] = isTrailers ? 128 : 0;
|
|
50
|
+
const view = new DataView(frame.buffer);
|
|
51
|
+
view.setUint32(1, data.length, false);
|
|
52
|
+
frame.set(data, 5);
|
|
53
|
+
return frame;
|
|
54
|
+
}
|
|
55
|
+
function decodeGrpcFrames(data) {
|
|
56
|
+
const frames = [];
|
|
57
|
+
let offset = 0;
|
|
58
|
+
while (offset < data.length) {
|
|
59
|
+
if (offset + 5 > data.length)
|
|
60
|
+
break;
|
|
61
|
+
const flags = data[offset];
|
|
62
|
+
const isTrailers = (flags & 128) !== 0;
|
|
63
|
+
const view = new DataView(data.buffer, data.byteOffset + offset + 1, 4);
|
|
64
|
+
const length = view.getUint32(0, false);
|
|
65
|
+
if (offset + 5 + length > data.length)
|
|
66
|
+
break;
|
|
67
|
+
const payload = data.slice(offset + 5, offset + 5 + length);
|
|
68
|
+
frames.push({ isTrailers, payload });
|
|
69
|
+
offset += 5 + length;
|
|
70
|
+
}
|
|
71
|
+
return frames;
|
|
72
|
+
}
|
|
73
|
+
function parseTrailers(data) {
|
|
74
|
+
const decoder = new TextDecoder();
|
|
75
|
+
const text = decoder.decode(data);
|
|
76
|
+
const metadata = {};
|
|
77
|
+
for (const line of text.split('\r\n')) {
|
|
78
|
+
const colonIndex = line.indexOf(':');
|
|
79
|
+
if (colonIndex > 0) {
|
|
80
|
+
const key = line.slice(0, colonIndex).trim().toLowerCase();
|
|
81
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
82
|
+
metadata[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return metadata;
|
|
86
|
+
}
|
|
87
|
+
export class GrpcWebClient {
|
|
88
|
+
client;
|
|
89
|
+
options;
|
|
90
|
+
constructor(client, options) {
|
|
91
|
+
this.client = client;
|
|
92
|
+
this.options = {
|
|
93
|
+
baseUrl: options.baseUrl,
|
|
94
|
+
metadata: options.metadata ?? {},
|
|
95
|
+
timeout: options.timeout ?? 30000,
|
|
96
|
+
textFormat: options.textFormat ?? true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async unary(service, method, request, codec, options) {
|
|
100
|
+
const url = `${this.options.baseUrl}/${service}/${method}`;
|
|
101
|
+
const encoded = codec.encode(request);
|
|
102
|
+
const frame = encodeGrpcFrame(encoded);
|
|
103
|
+
const contentType = this.options.textFormat
|
|
104
|
+
? 'application/grpc-web-text'
|
|
105
|
+
: 'application/grpc-web+proto';
|
|
106
|
+
const body = this.options.textFormat
|
|
107
|
+
? btoa(String.fromCharCode(...frame))
|
|
108
|
+
: frame;
|
|
109
|
+
const headers = {
|
|
110
|
+
'Content-Type': contentType,
|
|
111
|
+
'Accept': contentType,
|
|
112
|
+
'X-Grpc-Web': '1',
|
|
113
|
+
...this.options.metadata,
|
|
114
|
+
...options?.metadata,
|
|
115
|
+
};
|
|
116
|
+
const requestOptions = {
|
|
117
|
+
headers,
|
|
118
|
+
timeout: options?.timeout ?? this.options.timeout,
|
|
119
|
+
signal: options?.signal,
|
|
120
|
+
};
|
|
121
|
+
const response = await this.client.post(url, body, requestOptions);
|
|
122
|
+
return this.parseUnaryResponse(response, codec);
|
|
123
|
+
}
|
|
124
|
+
async *serverStream(service, method, request, codec, options) {
|
|
125
|
+
const url = `${this.options.baseUrl}/${service}/${method}`;
|
|
126
|
+
const encoded = codec.encode(request);
|
|
127
|
+
const frame = encodeGrpcFrame(encoded);
|
|
128
|
+
const contentType = this.options.textFormat
|
|
129
|
+
? 'application/grpc-web-text'
|
|
130
|
+
: 'application/grpc-web+proto';
|
|
131
|
+
const body = this.options.textFormat
|
|
132
|
+
? btoa(String.fromCharCode(...frame))
|
|
133
|
+
: frame;
|
|
134
|
+
const headers = {
|
|
135
|
+
'Content-Type': contentType,
|
|
136
|
+
'Accept': contentType,
|
|
137
|
+
'X-Grpc-Web': '1',
|
|
138
|
+
...this.options.metadata,
|
|
139
|
+
...options?.metadata,
|
|
140
|
+
};
|
|
141
|
+
const response = await this.client.post(url, body, {
|
|
142
|
+
headers,
|
|
143
|
+
timeout: options?.timeout ?? this.options.timeout,
|
|
144
|
+
signal: options?.signal,
|
|
145
|
+
});
|
|
146
|
+
const stream = response.read();
|
|
147
|
+
if (!stream) {
|
|
148
|
+
throw new GrpcError({ code: GrpcStatusCode.INTERNAL, message: 'No response body' }, {});
|
|
149
|
+
}
|
|
150
|
+
const reader = stream.getReader();
|
|
151
|
+
let buffer = new Uint8Array(0);
|
|
152
|
+
try {
|
|
153
|
+
while (true) {
|
|
154
|
+
const { done, value } = await reader.read();
|
|
155
|
+
if (value) {
|
|
156
|
+
let chunk = value;
|
|
157
|
+
if (this.options.textFormat) {
|
|
158
|
+
const text = new TextDecoder().decode(value);
|
|
159
|
+
const decoded = atob(text);
|
|
160
|
+
chunk = new Uint8Array(decoded.length);
|
|
161
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
162
|
+
chunk[i] = decoded.charCodeAt(i);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const newBuffer = new Uint8Array(buffer.length + chunk.length);
|
|
166
|
+
newBuffer.set(buffer);
|
|
167
|
+
newBuffer.set(chunk, buffer.length);
|
|
168
|
+
buffer = newBuffer;
|
|
169
|
+
const frames = decodeGrpcFrames(buffer);
|
|
170
|
+
let consumedBytes = 0;
|
|
171
|
+
for (const frame of frames) {
|
|
172
|
+
consumedBytes += 5 + frame.payload.length;
|
|
173
|
+
if (frame.isTrailers) {
|
|
174
|
+
const trailers = parseTrailers(frame.payload);
|
|
175
|
+
const status = parseInt(trailers['grpc-status'] ?? '0', 10);
|
|
176
|
+
const message = trailers['grpc-message'] ?? '';
|
|
177
|
+
if (status !== GrpcStatusCode.OK) {
|
|
178
|
+
throw new GrpcError({ code: status, message }, trailers);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
yield codec.decode(frame.payload);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (consumedBytes > 0) {
|
|
186
|
+
buffer = buffer.slice(consumedBytes);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (done)
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
reader.releaseLock();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
service(serviceName, methods) {
|
|
198
|
+
const service = {};
|
|
199
|
+
for (const [methodName, _config] of Object.entries(methods)) {
|
|
200
|
+
service[methodName] = async (request, options) => {
|
|
201
|
+
const codec = jsonCodec();
|
|
202
|
+
return this.unary(serviceName, methodName, request, codec, options);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return service;
|
|
206
|
+
}
|
|
207
|
+
async parseUnaryResponse(response, codec) {
|
|
208
|
+
let data;
|
|
209
|
+
if (this.options.textFormat) {
|
|
210
|
+
const text = await response.text();
|
|
211
|
+
const decoded = atob(text);
|
|
212
|
+
data = new Uint8Array(decoded.length);
|
|
213
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
214
|
+
data[i] = decoded.charCodeAt(i);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const blob = await response.blob();
|
|
219
|
+
data = new Uint8Array(await blob.arrayBuffer());
|
|
220
|
+
}
|
|
221
|
+
const frames = decodeGrpcFrames(data);
|
|
222
|
+
let message;
|
|
223
|
+
let metadata = {};
|
|
224
|
+
let status = { code: GrpcStatusCode.OK, message: '' };
|
|
225
|
+
for (const frame of frames) {
|
|
226
|
+
if (frame.isTrailers) {
|
|
227
|
+
metadata = parseTrailers(frame.payload);
|
|
228
|
+
status = {
|
|
229
|
+
code: parseInt(metadata['grpc-status'] ?? '0', 10),
|
|
230
|
+
message: metadata['grpc-message'] ?? '',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
message = codec.decode(frame.payload);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const headerStatus = response.headers.get('grpc-status');
|
|
238
|
+
if (headerStatus) {
|
|
239
|
+
status.code = parseInt(headerStatus, 10);
|
|
240
|
+
status.message = response.headers.get('grpc-message') ?? '';
|
|
241
|
+
}
|
|
242
|
+
if (status.code !== GrpcStatusCode.OK) {
|
|
243
|
+
throw new GrpcError(status, metadata);
|
|
244
|
+
}
|
|
245
|
+
if (message === undefined) {
|
|
246
|
+
throw new GrpcError({ code: GrpcStatusCode.INTERNAL, message: 'No message in response' }, metadata);
|
|
247
|
+
}
|
|
248
|
+
return { message, metadata, status };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
export function createGrpcWebClient(client, options) {
|
|
252
|
+
return new GrpcWebClient(client, options);
|
|
253
|
+
}
|
|
254
|
+
export function grpcWeb() {
|
|
255
|
+
return (client) => {
|
|
256
|
+
client.grpcWeb = (options) => {
|
|
257
|
+
const baseUrl = options?.baseUrl ?? client.config?.baseUrl ?? '';
|
|
258
|
+
return createGrpcWebClient(client, { baseUrl, ...options });
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"har-player.d.ts","sourceRoot":"","sources":["../../src/plugins/har-player.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA6C,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"har-player.d.ts","sourceRoot":"","sources":["../../src/plugins/har-player.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA6C,MAAM,mBAAmB,CAAC;AAKtF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAgBD,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAuF3D"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { HttpResponse } from '../core/response.js';
|
|
3
|
+
import { ReckerError } from '../core/errors.js';
|
|
3
4
|
export function harPlayer(options) {
|
|
4
5
|
let entries = [];
|
|
5
6
|
try {
|
|
@@ -8,7 +9,11 @@ export function harPlayer(options) {
|
|
|
8
9
|
entries = har.log.entries;
|
|
9
10
|
}
|
|
10
11
|
catch (err) {
|
|
11
|
-
|
|
12
|
+
throw new ReckerError(`Failed to load HAR file: ${options.path}`, undefined, undefined, [
|
|
13
|
+
'Ensure the HAR file exists at the specified path.',
|
|
14
|
+
'Check that the file is valid JSON.',
|
|
15
|
+
'Verify the HAR file has the correct structure (log.entries).'
|
|
16
|
+
]);
|
|
12
17
|
}
|
|
13
18
|
const matchEntry = (req, entry) => {
|
|
14
19
|
if (req.method !== entry.request.method)
|
|
@@ -43,7 +48,11 @@ export function harPlayer(options) {
|
|
|
43
48
|
return new HttpResponse(nativeRes);
|
|
44
49
|
}
|
|
45
50
|
if (options.strict) {
|
|
46
|
-
throw new
|
|
51
|
+
throw new ReckerError(`[Recker HAR Player] No matching recording found for ${req.method} ${req.url}`, req, undefined, [
|
|
52
|
+
'Ensure the HAR file contains an entry for this URL/method.',
|
|
53
|
+
'Normalize query parameters or body to match the recorded request.',
|
|
54
|
+
'Regenerate the HAR with the exact same request.'
|
|
55
|
+
]);
|
|
47
56
|
}
|
|
48
57
|
return next(req);
|
|
49
58
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Client } from '../core/client.js';
|
|
2
|
+
export interface Variant {
|
|
3
|
+
url: string;
|
|
4
|
+
bandwidth?: number;
|
|
5
|
+
resolution?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Segment {
|
|
8
|
+
url: string;
|
|
9
|
+
duration: number;
|
|
10
|
+
sequence: number;
|
|
11
|
+
key?: KeyInfo;
|
|
12
|
+
}
|
|
13
|
+
export interface KeyInfo {
|
|
14
|
+
method: string;
|
|
15
|
+
uri?: string;
|
|
16
|
+
iv?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface HlsHooks {
|
|
19
|
+
onManifest?: (manifest: string, url: string) => string | void;
|
|
20
|
+
onVariantSelected?: (variants: Variant[], defaultSelected: Variant) => Variant | void;
|
|
21
|
+
onSegment?: (segment: Segment) => Segment | void | null;
|
|
22
|
+
onKey?: (key: KeyInfo) => KeyInfo | void;
|
|
23
|
+
}
|
|
24
|
+
export interface HlsOptions extends HlsHooks {
|
|
25
|
+
concurrency?: number;
|
|
26
|
+
merge?: boolean;
|
|
27
|
+
live?: boolean;
|
|
28
|
+
duration?: number;
|
|
29
|
+
onInfo?: (message: string) => void;
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
}
|
|
32
|
+
export declare function downloadHls(client: Client, manifestUrl: string, outputPath: string, options?: HlsOptions): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=hls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hls.d.ts","sourceRoot":"","sources":["../../src/plugins/hls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAK3C,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,QAAQ;IAEvB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAE9D,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;IAEtF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC;IAExD,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAmHD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,UAAe,iBA+JzB"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { createWriteStream } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { mkdir, readdir, readFile, rm } from 'node:fs/promises';
|
|
4
|
+
function parseM3u8(content, baseUrl) {
|
|
5
|
+
const lines = content.split('\n');
|
|
6
|
+
const segments = [];
|
|
7
|
+
let currentDuration = 0;
|
|
8
|
+
let mediaSequence = 0;
|
|
9
|
+
let targetDuration = 5;
|
|
10
|
+
let endList = false;
|
|
11
|
+
let currentKey;
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
const line = lines[i].trim();
|
|
14
|
+
if (!line)
|
|
15
|
+
continue;
|
|
16
|
+
if (line.startsWith('#EXT-X-TARGETDURATION:')) {
|
|
17
|
+
targetDuration = parseFloat(line.split(':')[1]);
|
|
18
|
+
}
|
|
19
|
+
else if (line.startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
|
|
20
|
+
mediaSequence = parseInt(line.split(':')[1], 10);
|
|
21
|
+
}
|
|
22
|
+
else if (line.startsWith('#EXT-X-ENDLIST')) {
|
|
23
|
+
endList = true;
|
|
24
|
+
}
|
|
25
|
+
else if (line.startsWith('#EXT-X-KEY:')) {
|
|
26
|
+
const attrs = line.substring(11);
|
|
27
|
+
const methodMatch = attrs.match(/METHOD=([^,]+)/);
|
|
28
|
+
const uriMatch = attrs.match(/URI="([^"]+)"/);
|
|
29
|
+
const ivMatch = attrs.match(/IV=([^,]+)/);
|
|
30
|
+
if (methodMatch) {
|
|
31
|
+
currentKey = {
|
|
32
|
+
method: methodMatch[1],
|
|
33
|
+
uri: uriMatch ? uriMatch[1] : undefined,
|
|
34
|
+
iv: ivMatch ? ivMatch[1] : undefined
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (line.startsWith('#EXTINF:')) {
|
|
39
|
+
const durationStr = line.substring(8).split(',', 1)[0];
|
|
40
|
+
currentDuration = parseFloat(durationStr);
|
|
41
|
+
}
|
|
42
|
+
else if (!line.startsWith('#')) {
|
|
43
|
+
let url = line;
|
|
44
|
+
if (!url.startsWith('http')) {
|
|
45
|
+
try {
|
|
46
|
+
const base = new URL(baseUrl);
|
|
47
|
+
const basePath = base.pathname.substring(0, base.pathname.lastIndexOf('/') + 1);
|
|
48
|
+
url = new URL(url, base.origin + basePath).toString();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
segments.push({
|
|
54
|
+
url,
|
|
55
|
+
duration: currentDuration,
|
|
56
|
+
sequence: 0,
|
|
57
|
+
key: currentKey
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
segments.forEach((seg, idx) => {
|
|
62
|
+
seg.sequence = mediaSequence + idx;
|
|
63
|
+
});
|
|
64
|
+
return { segments, targetDuration, endList, mediaSequence };
|
|
65
|
+
}
|
|
66
|
+
function parseVariants(content, baseUrl) {
|
|
67
|
+
const lines = content.split('\n');
|
|
68
|
+
const variants = [];
|
|
69
|
+
let currentBandwidth;
|
|
70
|
+
let currentResolution;
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i].trim();
|
|
73
|
+
if (!line)
|
|
74
|
+
continue;
|
|
75
|
+
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
|
76
|
+
const bwMatch = line.match(/BANDWIDTH=(\d+)/);
|
|
77
|
+
const resMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
|
78
|
+
if (bwMatch)
|
|
79
|
+
currentBandwidth = parseInt(bwMatch[1], 10);
|
|
80
|
+
if (resMatch)
|
|
81
|
+
currentResolution = resMatch[1];
|
|
82
|
+
}
|
|
83
|
+
else if (!line.startsWith('#')) {
|
|
84
|
+
let url = line;
|
|
85
|
+
if (!url.startsWith('http')) {
|
|
86
|
+
try {
|
|
87
|
+
const base = new URL(baseUrl);
|
|
88
|
+
const basePath = base.pathname.substring(0, base.pathname.lastIndexOf('/') + 1);
|
|
89
|
+
url = new URL(url, base.origin + basePath).toString();
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
}
|
|
93
|
+
variants.push({
|
|
94
|
+
url,
|
|
95
|
+
bandwidth: currentBandwidth,
|
|
96
|
+
resolution: currentResolution
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return variants;
|
|
101
|
+
}
|
|
102
|
+
export async function downloadHls(client, manifestUrl, outputPath, options = {}) {
|
|
103
|
+
const merge = options.merge !== false;
|
|
104
|
+
const concurrency = options.concurrency || 5;
|
|
105
|
+
const isLive = options.live === true;
|
|
106
|
+
const maxDuration = options.duration || (isLive ? Infinity : 0);
|
|
107
|
+
const info = options.onInfo || (() => { });
|
|
108
|
+
const error = options.onError || ((err) => { throw err; });
|
|
109
|
+
const seenSegments = new Set();
|
|
110
|
+
const outputDir = merge ? dirname(outputPath) : outputPath;
|
|
111
|
+
await mkdir(outputDir, { recursive: true });
|
|
112
|
+
const tempDir = join(outputDir, `.tmp_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
113
|
+
if (merge) {
|
|
114
|
+
await mkdir(tempDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
let currentManifestUrl = manifestUrl;
|
|
117
|
+
try {
|
|
118
|
+
let initialManifest = await client.get(currentManifestUrl).text();
|
|
119
|
+
if (options.onManifest) {
|
|
120
|
+
const modified = options.onManifest(initialManifest, currentManifestUrl);
|
|
121
|
+
if (typeof modified === 'string')
|
|
122
|
+
initialManifest = modified;
|
|
123
|
+
}
|
|
124
|
+
if (initialManifest.includes('#EXT-X-STREAM-INF')) {
|
|
125
|
+
const variants = parseVariants(initialManifest, currentManifestUrl);
|
|
126
|
+
if (variants.length > 0) {
|
|
127
|
+
let selected = variants[variants.length - 1];
|
|
128
|
+
if (options.onVariantSelected) {
|
|
129
|
+
const userSelected = options.onVariantSelected(variants, selected);
|
|
130
|
+
if (userSelected)
|
|
131
|
+
selected = userSelected;
|
|
132
|
+
}
|
|
133
|
+
currentManifestUrl = selected.url;
|
|
134
|
+
info(`Master playlist detected. Switching to variant: ${currentManifestUrl}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
error(new Error(`Failed to fetch initial manifest: ${err}`));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const { RequestRunner } = await import('../runner/request-runner.js');
|
|
143
|
+
const runner = new RequestRunner({ concurrency });
|
|
144
|
+
const startTime = Date.now();
|
|
145
|
+
let recording = true;
|
|
146
|
+
while (recording) {
|
|
147
|
+
try {
|
|
148
|
+
let content = await client.get(currentManifestUrl).text();
|
|
149
|
+
if (options.onManifest) {
|
|
150
|
+
const modified = options.onManifest(content, currentManifestUrl);
|
|
151
|
+
if (typeof modified === 'string')
|
|
152
|
+
content = modified;
|
|
153
|
+
}
|
|
154
|
+
const playlist = parseM3u8(content, currentManifestUrl);
|
|
155
|
+
let newSegments = playlist.segments.filter(s => !seenSegments.has(s.url));
|
|
156
|
+
if (options.onSegment) {
|
|
157
|
+
const filtered = [];
|
|
158
|
+
for (const seg of newSegments) {
|
|
159
|
+
if (seg.key && options.onKey) {
|
|
160
|
+
const modifiedKey = options.onKey(seg.key);
|
|
161
|
+
if (modifiedKey)
|
|
162
|
+
seg.key = modifiedKey;
|
|
163
|
+
}
|
|
164
|
+
const res = options.onSegment(seg);
|
|
165
|
+
if (res === null)
|
|
166
|
+
continue;
|
|
167
|
+
if (res)
|
|
168
|
+
filtered.push(res);
|
|
169
|
+
else
|
|
170
|
+
filtered.push(seg);
|
|
171
|
+
}
|
|
172
|
+
newSegments = filtered;
|
|
173
|
+
}
|
|
174
|
+
if (newSegments.length > 0) {
|
|
175
|
+
info(`Found ${newSegments.length} new segments.`);
|
|
176
|
+
await runner.run(newSegments, async (seg) => {
|
|
177
|
+
seenSegments.add(seg.url);
|
|
178
|
+
const fileDest = merge
|
|
179
|
+
? join(tempDir, `${seg.sequence.toString().padStart(10, '0')}.ts`)
|
|
180
|
+
: join(outputDir, `segment_${seg.sequence}.ts`);
|
|
181
|
+
await client.get(seg.url).write(fileDest);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (!isLive || playlist.endList) {
|
|
185
|
+
recording = false;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (maxDuration > 0 && (Date.now() - startTime) > maxDuration) {
|
|
189
|
+
info('Max duration reached. Stopping.');
|
|
190
|
+
recording = false;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
const waitTime = Math.max(1000, (playlist.targetDuration * 1000) / 2);
|
|
194
|
+
await new Promise(r => setTimeout(r, waitTime));
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
if (isLive) {
|
|
198
|
+
info(`Error fetching live manifest, retrying: ${err.message}`);
|
|
199
|
+
const waitTime = 5000;
|
|
200
|
+
await new Promise(r => setTimeout(r, waitTime));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
error(new Error(`HLS download failed: ${err.message}`));
|
|
204
|
+
recording = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (merge) {
|
|
209
|
+
info('Merging segments...');
|
|
210
|
+
const dest = createWriteStream(outputPath);
|
|
211
|
+
const files = (await readdir(tempDir)).sort();
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
if (!file.endsWith('.ts'))
|
|
214
|
+
continue;
|
|
215
|
+
const chunk = await readFile(join(tempDir, file));
|
|
216
|
+
dest.write(chunk);
|
|
217
|
+
}
|
|
218
|
+
dest.end();
|
|
219
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
220
|
+
info(`Saved to ${outputPath}`);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
info(`Download complete.`);
|
|
224
|
+
}
|
|
225
|
+
}
|