recker 1.0.26 → 1.0.27

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.
Files changed (171) hide show
  1. package/dist/browser/browser/cache.d.ts +40 -0
  2. package/dist/browser/browser/cache.js +199 -0
  3. package/dist/browser/browser/crypto.d.ts +24 -0
  4. package/dist/browser/browser/crypto.js +80 -0
  5. package/dist/browser/browser/index.d.ts +31 -0
  6. package/dist/browser/browser/index.js +31 -0
  7. package/dist/browser/browser/recker.d.ts +26 -0
  8. package/dist/browser/browser/recker.js +61 -0
  9. package/dist/browser/cache/basic-file-storage.d.ts +12 -0
  10. package/dist/browser/cache/basic-file-storage.js +50 -0
  11. package/dist/browser/cache/memory-limits.d.ts +20 -0
  12. package/dist/browser/cache/memory-limits.js +96 -0
  13. package/dist/browser/cache/memory-storage.d.ts +132 -0
  14. package/dist/browser/cache/memory-storage.js +454 -0
  15. package/dist/browser/cache.d.ts +40 -0
  16. package/dist/browser/cache.js +199 -0
  17. package/dist/browser/constants/http-status.d.ts +73 -0
  18. package/dist/browser/constants/http-status.js +156 -0
  19. package/dist/browser/cookies/memory-cookie-jar.d.ts +30 -0
  20. package/dist/browser/cookies/memory-cookie-jar.js +210 -0
  21. package/dist/browser/core/client.d.ts +118 -0
  22. package/dist/browser/core/client.js +667 -0
  23. package/dist/browser/core/errors.d.ts +142 -0
  24. package/dist/browser/core/errors.js +308 -0
  25. package/dist/browser/core/index.d.ts +5 -0
  26. package/dist/browser/core/index.js +5 -0
  27. package/dist/browser/core/request-promise.d.ts +23 -0
  28. package/dist/browser/core/request-promise.js +82 -0
  29. package/dist/browser/core/request.d.ts +20 -0
  30. package/dist/browser/core/request.js +76 -0
  31. package/dist/browser/core/response.d.ts +34 -0
  32. package/dist/browser/core/response.js +178 -0
  33. package/dist/browser/crypto.d.ts +24 -0
  34. package/dist/browser/crypto.js +80 -0
  35. package/dist/browser/index.d.ts +31 -0
  36. package/dist/browser/index.js +31 -0
  37. package/dist/browser/plugins/auth/api-key.d.ts +8 -0
  38. package/dist/browser/plugins/auth/api-key.js +27 -0
  39. package/dist/browser/plugins/auth/auth0.d.ts +33 -0
  40. package/dist/browser/plugins/auth/auth0.js +94 -0
  41. package/dist/browser/plugins/auth/aws-sigv4.d.ts +10 -0
  42. package/dist/browser/plugins/auth/aws-sigv4.js +88 -0
  43. package/dist/browser/plugins/auth/azure-ad.d.ts +48 -0
  44. package/dist/browser/plugins/auth/azure-ad.js +152 -0
  45. package/dist/browser/plugins/auth/basic.d.ts +7 -0
  46. package/dist/browser/plugins/auth/basic.js +13 -0
  47. package/dist/browser/plugins/auth/bearer.d.ts +8 -0
  48. package/dist/browser/plugins/auth/bearer.js +17 -0
  49. package/dist/browser/plugins/auth/cognito.d.ts +45 -0
  50. package/dist/browser/plugins/auth/cognito.js +208 -0
  51. package/dist/browser/plugins/auth/digest.d.ts +8 -0
  52. package/dist/browser/plugins/auth/digest.js +100 -0
  53. package/dist/browser/plugins/auth/firebase.d.ts +32 -0
  54. package/dist/browser/plugins/auth/firebase.js +195 -0
  55. package/dist/browser/plugins/auth/github-app.d.ts +36 -0
  56. package/dist/browser/plugins/auth/github-app.js +170 -0
  57. package/dist/browser/plugins/auth/google-service-account.d.ts +49 -0
  58. package/dist/browser/plugins/auth/google-service-account.js +172 -0
  59. package/dist/browser/plugins/auth/index.d.ts +15 -0
  60. package/dist/browser/plugins/auth/index.js +15 -0
  61. package/dist/browser/plugins/auth/mtls.d.ts +37 -0
  62. package/dist/browser/plugins/auth/mtls.js +140 -0
  63. package/dist/browser/plugins/auth/oauth2.d.ts +8 -0
  64. package/dist/browser/plugins/auth/oauth2.js +26 -0
  65. package/dist/browser/plugins/auth/oidc.d.ts +55 -0
  66. package/dist/browser/plugins/auth/oidc.js +222 -0
  67. package/dist/browser/plugins/auth/okta.d.ts +47 -0
  68. package/dist/browser/plugins/auth/okta.js +157 -0
  69. package/dist/browser/plugins/auth.d.ts +1 -0
  70. package/dist/browser/plugins/auth.js +1 -0
  71. package/dist/browser/plugins/cache.d.ts +15 -0
  72. package/dist/browser/plugins/cache.js +486 -0
  73. package/dist/browser/plugins/circuit-breaker.d.ts +13 -0
  74. package/dist/browser/plugins/circuit-breaker.js +100 -0
  75. package/dist/browser/plugins/compression.d.ts +4 -0
  76. package/dist/browser/plugins/compression.js +130 -0
  77. package/dist/browser/plugins/cookie-jar.d.ts +5 -0
  78. package/dist/browser/plugins/cookie-jar.js +72 -0
  79. package/dist/browser/plugins/dedup.d.ts +5 -0
  80. package/dist/browser/plugins/dedup.js +35 -0
  81. package/dist/browser/plugins/graphql.d.ts +13 -0
  82. package/dist/browser/plugins/graphql.js +58 -0
  83. package/dist/browser/plugins/grpc-web.d.ts +79 -0
  84. package/dist/browser/plugins/grpc-web.js +261 -0
  85. package/dist/browser/plugins/hls.d.ts +105 -0
  86. package/dist/browser/plugins/hls.js +395 -0
  87. package/dist/browser/plugins/jsonrpc.d.ts +75 -0
  88. package/dist/browser/plugins/jsonrpc.js +143 -0
  89. package/dist/browser/plugins/logger.d.ts +13 -0
  90. package/dist/browser/plugins/logger.js +108 -0
  91. package/dist/browser/plugins/odata.d.ts +181 -0
  92. package/dist/browser/plugins/odata.js +564 -0
  93. package/dist/browser/plugins/pagination.d.ts +16 -0
  94. package/dist/browser/plugins/pagination.js +105 -0
  95. package/dist/browser/plugins/rate-limit.d.ts +15 -0
  96. package/dist/browser/plugins/rate-limit.js +162 -0
  97. package/dist/browser/plugins/retry.d.ts +14 -0
  98. package/dist/browser/plugins/retry.js +116 -0
  99. package/dist/browser/plugins/scrape.d.ts +21 -0
  100. package/dist/browser/plugins/scrape.js +82 -0
  101. package/dist/browser/plugins/server-timing.d.ts +7 -0
  102. package/dist/browser/plugins/server-timing.js +24 -0
  103. package/dist/browser/plugins/soap.d.ts +72 -0
  104. package/dist/browser/plugins/soap.js +347 -0
  105. package/dist/browser/plugins/xml.d.ts +9 -0
  106. package/dist/browser/plugins/xml.js +194 -0
  107. package/dist/browser/plugins/xsrf.d.ts +9 -0
  108. package/dist/browser/plugins/xsrf.js +48 -0
  109. package/dist/browser/recker.d.ts +26 -0
  110. package/dist/browser/recker.js +61 -0
  111. package/dist/browser/runner/request-runner.d.ts +46 -0
  112. package/dist/browser/runner/request-runner.js +89 -0
  113. package/dist/browser/scrape/document.d.ts +44 -0
  114. package/dist/browser/scrape/document.js +210 -0
  115. package/dist/browser/scrape/element.d.ts +49 -0
  116. package/dist/browser/scrape/element.js +176 -0
  117. package/dist/browser/scrape/extractors.d.ts +16 -0
  118. package/dist/browser/scrape/extractors.js +356 -0
  119. package/dist/browser/scrape/types.d.ts +107 -0
  120. package/dist/browser/scrape/types.js +1 -0
  121. package/dist/browser/transport/fetch.d.ts +11 -0
  122. package/dist/browser/transport/fetch.js +143 -0
  123. package/dist/browser/transport/undici.d.ts +38 -0
  124. package/dist/browser/transport/undici.js +897 -0
  125. package/dist/browser/types/ai.d.ts +267 -0
  126. package/dist/browser/types/ai.js +1 -0
  127. package/dist/browser/types/index.d.ts +351 -0
  128. package/dist/browser/types/index.js +1 -0
  129. package/dist/browser/types/logger.d.ts +16 -0
  130. package/dist/browser/types/logger.js +66 -0
  131. package/dist/browser/types/udp.d.ts +138 -0
  132. package/dist/browser/types/udp.js +1 -0
  133. package/dist/browser/utils/agent-manager.d.ts +29 -0
  134. package/dist/browser/utils/agent-manager.js +160 -0
  135. package/dist/browser/utils/body.d.ts +10 -0
  136. package/dist/browser/utils/body.js +148 -0
  137. package/dist/browser/utils/charset.d.ts +15 -0
  138. package/dist/browser/utils/charset.js +169 -0
  139. package/dist/browser/utils/concurrency.d.ts +20 -0
  140. package/dist/browser/utils/concurrency.js +120 -0
  141. package/dist/browser/utils/dns.d.ts +6 -0
  142. package/dist/browser/utils/dns.js +26 -0
  143. package/dist/browser/utils/header-parser.d.ts +94 -0
  144. package/dist/browser/utils/header-parser.js +617 -0
  145. package/dist/browser/utils/html-cleaner.d.ts +1 -0
  146. package/dist/browser/utils/html-cleaner.js +21 -0
  147. package/dist/browser/utils/link-header.d.ts +69 -0
  148. package/dist/browser/utils/link-header.js +190 -0
  149. package/dist/browser/utils/optional-require.d.ts +19 -0
  150. package/dist/browser/utils/optional-require.js +105 -0
  151. package/dist/browser/utils/progress.d.ts +8 -0
  152. package/dist/browser/utils/progress.js +82 -0
  153. package/dist/browser/utils/request-pool.d.ts +22 -0
  154. package/dist/browser/utils/request-pool.js +101 -0
  155. package/dist/browser/utils/sse.d.ts +7 -0
  156. package/dist/browser/utils/sse.js +67 -0
  157. package/dist/browser/utils/streaming.d.ts +17 -0
  158. package/dist/browser/utils/streaming.js +84 -0
  159. package/dist/browser/utils/try-fn.d.ts +3 -0
  160. package/dist/browser/utils/try-fn.js +59 -0
  161. package/dist/browser/utils/user-agent.d.ts +44 -0
  162. package/dist/browser/utils/user-agent.js +100 -0
  163. package/dist/browser/utils/whois.d.ts +32 -0
  164. package/dist/browser/utils/whois.js +246 -0
  165. package/dist/browser/websocket/client.d.ts +65 -0
  166. package/dist/browser/websocket/client.js +313 -0
  167. package/dist/cli/index.d.ts +1 -0
  168. package/dist/cli/index.js +1 -0
  169. package/dist/transport/fetch.d.ts +7 -1
  170. package/dist/transport/fetch.js +58 -76
  171. package/package.json +34 -2
@@ -0,0 +1,130 @@
1
+ import { gzip, deflate, brotliCompress } from 'node:zlib';
2
+ import { promisify } from 'node:util';
3
+ import { UnsupportedError } from '../core/errors.js';
4
+ const gzipAsync = promisify(gzip);
5
+ const deflateAsync = promisify(deflate);
6
+ const brotliAsync = promisify(brotliCompress);
7
+ function isCompressible(contentType) {
8
+ if (!contentType)
9
+ return true;
10
+ const compressibleTypes = [
11
+ 'text/',
12
+ 'application/json',
13
+ 'application/xml',
14
+ 'application/javascript',
15
+ 'application/x-www-form-urlencoded'
16
+ ];
17
+ return compressibleTypes.some(type => contentType.includes(type));
18
+ }
19
+ function getBodySize(body) {
20
+ if (!body)
21
+ return 0;
22
+ if (typeof body === 'string') {
23
+ return Buffer.byteLength(body, 'utf8');
24
+ }
25
+ if (body instanceof Buffer) {
26
+ return body.length;
27
+ }
28
+ if (body instanceof ArrayBuffer) {
29
+ return body.byteLength;
30
+ }
31
+ if (body instanceof Blob) {
32
+ return body.size;
33
+ }
34
+ if (typeof body === 'object') {
35
+ try {
36
+ return Buffer.byteLength(JSON.stringify(body), 'utf8');
37
+ }
38
+ catch {
39
+ return 0;
40
+ }
41
+ }
42
+ return 0;
43
+ }
44
+ function toBuffer(body) {
45
+ if (!body)
46
+ return null;
47
+ if (Buffer.isBuffer(body)) {
48
+ return body;
49
+ }
50
+ if (typeof body === 'string') {
51
+ return Buffer.from(body, 'utf8');
52
+ }
53
+ if (body instanceof ArrayBuffer) {
54
+ return Buffer.from(body);
55
+ }
56
+ if (typeof body === 'object') {
57
+ try {
58
+ return Buffer.from(JSON.stringify(body), 'utf8');
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ async function compress(data, algorithm) {
67
+ switch (algorithm) {
68
+ case 'gzip':
69
+ return await gzipAsync(data);
70
+ case 'deflate':
71
+ return await deflateAsync(data);
72
+ case 'br':
73
+ return await brotliAsync(data);
74
+ default:
75
+ throw new UnsupportedError(`Unsupported compression algorithm: ${algorithm}`, {
76
+ feature: algorithm,
77
+ });
78
+ }
79
+ }
80
+ export function compression(options = {}) {
81
+ const { algorithm = 'gzip', threshold = 1024, force = false, methods = ['POST', 'PUT', 'PATCH'] } = options;
82
+ return async (req, next) => {
83
+ const shouldCompress = methods.includes(req.method);
84
+ if (!shouldCompress || !req.body) {
85
+ return next(req);
86
+ }
87
+ if (req.headers.has('Content-Encoding')) {
88
+ return next(req);
89
+ }
90
+ const contentType = req.headers.get('Content-Type');
91
+ if (!isCompressible(contentType)) {
92
+ return next(req);
93
+ }
94
+ const bodySize = getBodySize(req.body);
95
+ if (!force && bodySize < threshold) {
96
+ return next(req);
97
+ }
98
+ const buffer = toBuffer(req.body);
99
+ if (!buffer) {
100
+ return next(req);
101
+ }
102
+ try {
103
+ const compressed = await compress(buffer, algorithm);
104
+ if (!force && compressed.length >= buffer.length) {
105
+ return next(req);
106
+ }
107
+ const newHeaders = new Headers(req.headers);
108
+ newHeaders.set('Content-Encoding', algorithm);
109
+ newHeaders.set('Content-Length', compressed.length.toString());
110
+ const compressedReq = {
111
+ ...req,
112
+ body: compressed,
113
+ headers: newHeaders
114
+ };
115
+ return next(compressedReq);
116
+ }
117
+ catch {
118
+ return next(req);
119
+ }
120
+ };
121
+ }
122
+ export function createCompressionMiddleware(config) {
123
+ if (!config) {
124
+ return null;
125
+ }
126
+ if (config === true) {
127
+ return compression();
128
+ }
129
+ return compression(config);
130
+ }
@@ -0,0 +1,5 @@
1
+ import { Plugin } from '../types/index.js';
2
+ export interface CookieJarOptions {
3
+ store?: Map<string, string>;
4
+ }
5
+ export declare function cookieJarPlugin(options?: CookieJarOptions): Plugin;
@@ -0,0 +1,72 @@
1
+ export function cookieJarPlugin(options = {}) {
2
+ const store = new Map();
3
+ if (options.store) {
4
+ if (options.store instanceof Map) {
5
+ store.set('*', options.store);
6
+ }
7
+ }
8
+ return (client) => {
9
+ client.beforeRequest((req) => {
10
+ const url = new URL(req.url);
11
+ const hostname = url.hostname;
12
+ const cookieList = [];
13
+ const addCookies = (domainStore) => {
14
+ if (domainStore) {
15
+ domainStore.forEach((value, key) => {
16
+ cookieList.push(`${key}=${value}`);
17
+ });
18
+ }
19
+ };
20
+ addCookies(store.get(hostname));
21
+ for (const [domain, domainStore] of store.entries()) {
22
+ if (domain !== '*' && domain !== hostname && hostname.endsWith(domain)) {
23
+ addCookies(domainStore);
24
+ }
25
+ if (domain === '*') {
26
+ addCookies(domainStore);
27
+ }
28
+ }
29
+ if (cookieList.length > 0) {
30
+ const existing = req.headers.get('cookie');
31
+ const newCookies = cookieList.join('; ');
32
+ req.headers.set('cookie', existing ? `${existing}; ${newCookies}` : newCookies);
33
+ }
34
+ });
35
+ client.afterResponse((req, res) => {
36
+ const setCookie = res.headers.get('set-cookie');
37
+ if (!setCookie)
38
+ return;
39
+ const url = new URL(req.url);
40
+ let domain = url.hostname;
41
+ const cookies = splitCookies(setCookie);
42
+ cookies.forEach(cookieStr => {
43
+ const parts = cookieStr.split(';');
44
+ const [nameValue] = parts;
45
+ if (!nameValue)
46
+ return;
47
+ const [key, ...valParts] = nameValue.split('=');
48
+ if (!key)
49
+ return;
50
+ const value = valParts.join('=').trim();
51
+ let specificDomain = domain;
52
+ const domainPart = parts.find(p => p.trim().toLowerCase().startsWith('domain='));
53
+ if (domainPart) {
54
+ const d = domainPart.split('=')[1]?.trim();
55
+ if (d)
56
+ specificDomain = d;
57
+ }
58
+ if (!store.has(specificDomain)) {
59
+ store.set(specificDomain, new Map());
60
+ }
61
+ const domainStore = store.get(specificDomain);
62
+ domainStore.set(key.trim(), value);
63
+ if (options.store) {
64
+ options.store.set(key.trim(), value);
65
+ }
66
+ });
67
+ });
68
+ };
69
+ }
70
+ function splitCookies(header) {
71
+ return header.split(/,(?=\s*[a-zA-Z0-9_]+=[^;])/g);
72
+ }
@@ -0,0 +1,5 @@
1
+ import { Plugin, ReckerRequest } from '../types/index.js';
2
+ export interface DedupOptions {
3
+ keyGenerator?: (req: ReckerRequest) => string;
4
+ }
5
+ export declare function dedupPlugin(options?: DedupOptions): Plugin;
@@ -0,0 +1,35 @@
1
+ const keyBuffer = { method: '', url: '' };
2
+ export function dedupPlugin(options = {}) {
3
+ const pendingRequests = new Map();
4
+ const generateKey = options.keyGenerator || ((req) => {
5
+ return req.method + ':' + req.url;
6
+ });
7
+ const dedupMiddleware = async (req, next) => {
8
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
9
+ return next(req);
10
+ }
11
+ const key = generateKey(req);
12
+ if (pendingRequests.has(key)) {
13
+ const response = await pendingRequests.get(key);
14
+ return response.clone();
15
+ }
16
+ const promise = next(req)
17
+ .then((res) => {
18
+ return res;
19
+ })
20
+ .finally(() => {
21
+ pendingRequests.delete(key);
22
+ });
23
+ pendingRequests.set(key, promise);
24
+ try {
25
+ const response = await promise;
26
+ return response.clone ? response.clone() : response;
27
+ }
28
+ catch (err) {
29
+ throw err;
30
+ }
31
+ };
32
+ return (client) => {
33
+ client.use(dedupMiddleware);
34
+ };
35
+ }
@@ -0,0 +1,13 @@
1
+ import { Plugin, ReckerRequest, ReckerResponse } from '../types/index.js';
2
+ import { Client } from '../core/client.js';
3
+ import { ReckerError } from '../core/errors.js';
4
+ export declare class GraphQLError extends ReckerError {
5
+ errors: any[];
6
+ response: ReckerResponse;
7
+ constructor(errors: any[], response: ReckerResponse, request?: ReckerRequest);
8
+ }
9
+ export interface GraphQLOptions {
10
+ throwOnErrors?: boolean;
11
+ }
12
+ export declare function graphqlPlugin(options?: GraphQLOptions): Plugin;
13
+ export declare function graphql<T = any>(client: Client, query: string, variables?: Record<string, any>, options?: any): Promise<T>;
@@ -0,0 +1,58 @@
1
+ import { ReckerError } from '../core/errors.js';
2
+ export class GraphQLError extends ReckerError {
3
+ errors;
4
+ response;
5
+ constructor(errors, response, request) {
6
+ const message = errors?.[0]?.message || 'GraphQL response contains errors';
7
+ const suggestions = [
8
+ 'Check the GraphQL query and variables for schema compliance.',
9
+ 'Inspect the GraphQL errors array for details.',
10
+ 'Fix validation errors before retrying; network errors may be retriable.'
11
+ ];
12
+ super(message, request, response, suggestions, false);
13
+ this.errors = errors;
14
+ this.response = response;
15
+ this.name = 'GraphQLError';
16
+ }
17
+ }
18
+ export function graphqlPlugin(options = {}) {
19
+ const throwOnErrors = options.throwOnErrors !== false;
20
+ const middleware = async (req, next) => {
21
+ const res = await next(req);
22
+ const contentType = res.headers.get('content-type');
23
+ if (throwOnErrors && contentType && contentType.includes('application/json')) {
24
+ const clone = res.clone();
25
+ try {
26
+ const body = await clone.json();
27
+ if (body && Array.isArray(body.errors) && body.errors.length > 0) {
28
+ throw new GraphQLError(body.errors, res);
29
+ }
30
+ }
31
+ catch (err) {
32
+ if (err instanceof GraphQLError)
33
+ throw err;
34
+ }
35
+ }
36
+ return res;
37
+ };
38
+ return (client) => {
39
+ client.use(middleware);
40
+ };
41
+ }
42
+ export async function graphql(client, query, variables = {}, options = {}) {
43
+ const opMatch = query.match(/(query|mutation|subscription)\s+([a-zA-Z0-9_]+)/);
44
+ const operationName = opMatch ? opMatch[2] : undefined;
45
+ const payload = { query, variables, operationName };
46
+ if (options.method === 'GET') {
47
+ const params = {
48
+ query,
49
+ variables: JSON.stringify(variables),
50
+ ...(operationName && { operationName })
51
+ };
52
+ options.params = { ...options.params, ...params };
53
+ const res = await client.get('', options).json();
54
+ return res.data;
55
+ }
56
+ const res = await client.post('', payload, options).json();
57
+ return res.data;
58
+ }
@@ -0,0 +1,79 @@
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
+ }
@@ -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
+ }