@dxos/edge-client 0.6.12-main.78ddbdf → 0.6.12-main.89e9959

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.
@@ -0,0 +1,153 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { sleep } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { type SpaceId } from '@dxos/keys';
8
+ import { log } from '@dxos/log';
9
+ import {
10
+ EdgeCallFailedError,
11
+ type EdgeHttpResponse,
12
+ type GetNotarizationResponseBody,
13
+ type PostNotarizationRequestBody,
14
+ } from '@dxos/protocols';
15
+
16
+ const DEFAULT_RETRY_TIMEOUT = 1500;
17
+ const DEFAULT_RETRY_JITTER = 500;
18
+ const DEFAULT_MAX_RETRIES_COUNT = 3;
19
+
20
+ export class EdgeHttpClient {
21
+ private readonly _baseUrl: string;
22
+
23
+ constructor(baseUrl: string) {
24
+ const url = new URL(baseUrl);
25
+ url.protocol = 'https';
26
+ this._baseUrl = url.toString();
27
+ log('created', { url: this._baseUrl });
28
+ }
29
+
30
+ public getCredentialsForNotarization(spaceId: SpaceId, args?: EdgeHttpGetArgs): Promise<GetNotarizationResponseBody> {
31
+ return this._call(`/spaces/${spaceId}/notarization`, { ...args, method: 'GET' });
32
+ }
33
+
34
+ public async notarizeCredentials(
35
+ spaceId: SpaceId,
36
+ body: PostNotarizationRequestBody,
37
+ args?: EdgeHttpGetArgs,
38
+ ): Promise<void> {
39
+ await this._call(`/spaces/${spaceId}/notarization`, { ...args, body, method: 'POST' });
40
+ }
41
+
42
+ private async _call<T>(path: string, args: EdgeHttpCallArgs): Promise<T> {
43
+ const requestContext = args.context ?? new Context();
44
+ const shouldRetry = createRetryHandler(args);
45
+ const request = createRequest(args);
46
+ const url = `${this._baseUrl}${path.startsWith('/') ? path.slice(1) : path}`;
47
+
48
+ log.info('call', { method: args.method, path });
49
+
50
+ while (true) {
51
+ let processingError: EdgeCallFailedError;
52
+ let retryAfterHeaderValue: number = Number.NaN;
53
+ try {
54
+ const response = await fetch(url, request);
55
+
56
+ retryAfterHeaderValue = Number(response.headers.get('Retry-After'));
57
+
58
+ if (response.ok) {
59
+ const body = (await response.json()) as EdgeHttpResponse<T>;
60
+ if (body.success) {
61
+ return body.data;
62
+ }
63
+
64
+ const isNonRetryable = body.errorData != null;
65
+ if (isNonRetryable) {
66
+ throw new EdgeCallFailedError(body.reason, body.errorData);
67
+ }
68
+
69
+ processingError = new EdgeCallFailedError(body.reason);
70
+ } else {
71
+ processingError = EdgeCallFailedError.fromFailureResponse(response);
72
+ if (!isRetryable(response.status)) {
73
+ throw processingError;
74
+ }
75
+ }
76
+ } catch (error: any) {
77
+ processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
78
+ }
79
+
80
+ if (await shouldRetry(requestContext, retryAfterHeaderValue)) {
81
+ log.info('retrying edge request', { path, processingError });
82
+ } else {
83
+ throw processingError;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ const createRequest = (args: EdgeHttpCallArgs): RequestInit => {
90
+ return {
91
+ method: args.method,
92
+ body: args.body && JSON.stringify(args.body),
93
+ };
94
+ };
95
+
96
+ const isRetryable = (status: number) => {
97
+ if (status === 501) {
98
+ // Not Implemented
99
+ return false;
100
+ }
101
+ // TODO: handle 401 Not Authorized
102
+ return !(status >= 400 && status < 500);
103
+ };
104
+
105
+ const createRetryHandler = (args: EdgeHttpCallArgs) => {
106
+ if (!args.retry || args.retry.count < 1) {
107
+ return async () => false;
108
+ }
109
+ let retries = 0;
110
+ const maxRetries = args.retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
111
+ const baseTimeout = args.retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
112
+ const jitter = args.retry.jitter ?? DEFAULT_RETRY_JITTER;
113
+ return async (ctx: Context, retryAfter: number) => {
114
+ if (++retries > maxRetries || ctx.disposed) {
115
+ return false;
116
+ }
117
+
118
+ if (retryAfter) {
119
+ await sleep(retryAfter);
120
+ } else {
121
+ const timeout = baseTimeout + Math.random() * jitter;
122
+ await sleep(timeout);
123
+ }
124
+
125
+ return true;
126
+ };
127
+ };
128
+
129
+ export type RetryConfig = {
130
+ /**
131
+ * A number of call retries, not counting the initial request.
132
+ */
133
+ count: number;
134
+ /**
135
+ * Delay before retries in ms.
136
+ */
137
+ timeout?: number;
138
+ /**
139
+ * A random amount of time before retrying to help prevent large bursts of requests.
140
+ */
141
+ jitter?: number;
142
+ };
143
+
144
+ export type EdgeHttpGetArgs = { context?: Context; retry?: RetryConfig };
145
+
146
+ export type EdgeHttpPostArgs = { context?: Context; body?: any; retry?: RetryConfig };
147
+
148
+ type EdgeHttpCallArgs = {
149
+ method: string;
150
+ body?: any;
151
+ context?: Context;
152
+ retry?: RetryConfig;
153
+ };
package/src/index.ts CHANGED
@@ -8,3 +8,5 @@ export * from './edge-client';
8
8
  export * from './defs';
9
9
  export * from './protocol';
10
10
  export * from './errors';
11
+ export * from './auth';
12
+ export * from './edge-http-client';