@fluidframework/routerlicious-driver 2.5.0-302463 → 2.5.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/contracts.d.ts +6 -0
  3. package/dist/contracts.d.ts.map +1 -1
  4. package/dist/contracts.js +7 -0
  5. package/dist/contracts.js.map +1 -1
  6. package/dist/documentDeltaConnection.d.ts +5 -1
  7. package/dist/documentDeltaConnection.d.ts.map +1 -1
  8. package/dist/documentDeltaConnection.js +12 -2
  9. package/dist/documentDeltaConnection.js.map +1 -1
  10. package/dist/documentService.d.ts.map +1 -1
  11. package/dist/documentService.js +6 -7
  12. package/dist/documentService.js.map +1 -1
  13. package/dist/errorUtils.d.ts +31 -4
  14. package/dist/errorUtils.d.ts.map +1 -1
  15. package/dist/errorUtils.js +78 -8
  16. package/dist/errorUtils.js.map +1 -1
  17. package/dist/packageVersion.d.ts +1 -1
  18. package/dist/packageVersion.d.ts.map +1 -1
  19. package/dist/packageVersion.js +1 -1
  20. package/dist/packageVersion.js.map +1 -1
  21. package/dist/restWrapper.d.ts.map +1 -1
  22. package/dist/restWrapper.js +15 -10
  23. package/dist/restWrapper.js.map +1 -1
  24. package/dist/socketModule.d.ts +7 -0
  25. package/dist/socketModule.d.ts.map +1 -0
  26. package/dist/socketModule.js +11 -0
  27. package/dist/socketModule.js.map +1 -0
  28. package/lib/contracts.d.ts +6 -0
  29. package/lib/contracts.d.ts.map +1 -1
  30. package/lib/contracts.js +6 -1
  31. package/lib/contracts.js.map +1 -1
  32. package/lib/documentDeltaConnection.d.ts +5 -1
  33. package/lib/documentDeltaConnection.d.ts.map +1 -1
  34. package/lib/documentDeltaConnection.js +13 -3
  35. package/lib/documentDeltaConnection.js.map +1 -1
  36. package/lib/documentService.d.ts.map +1 -1
  37. package/lib/documentService.js +6 -4
  38. package/lib/documentService.js.map +1 -1
  39. package/lib/errorUtils.d.ts +31 -4
  40. package/lib/errorUtils.d.ts.map +1 -1
  41. package/lib/errorUtils.js +75 -7
  42. package/lib/errorUtils.js.map +1 -1
  43. package/lib/packageVersion.d.ts +1 -1
  44. package/lib/packageVersion.d.ts.map +1 -1
  45. package/lib/packageVersion.js +1 -1
  46. package/lib/packageVersion.js.map +1 -1
  47. package/lib/restWrapper.d.ts.map +1 -1
  48. package/lib/restWrapper.js +16 -11
  49. package/lib/restWrapper.js.map +1 -1
  50. package/lib/socketModule.d.ts +7 -0
  51. package/lib/socketModule.d.ts.map +1 -0
  52. package/lib/socketModule.js +8 -0
  53. package/lib/socketModule.js.map +1 -0
  54. package/package.json +11 -11
  55. package/src/contracts.ts +7 -0
  56. package/src/documentDeltaConnection.ts +32 -3
  57. package/src/documentService.ts +9 -5
  58. package/src/errorUtils.ts +103 -6
  59. package/src/packageVersion.ts +1 -1
  60. package/src/restWrapper.ts +29 -10
  61. package/src/socketModule.ts +9 -0
package/src/errorUtils.ts CHANGED
@@ -13,9 +13,11 @@ import {
13
13
  GenericNetworkError,
14
14
  NonRetryableError,
15
15
  createGenericNetworkError,
16
+ type DriverErrorTelemetryProps,
16
17
  } from "@fluidframework/driver-utils/internal";
17
- import { IFluidErrorBase } from "@fluidframework/telemetry-utils/internal";
18
+ import { IFluidErrorBase, LoggingError } from "@fluidframework/telemetry-utils/internal";
18
19
 
20
+ import { R11sServiceClusterDrainingErrorCode } from "./contracts.js";
19
21
  import { pkgVersion as driverVersion } from "./packageVersion.js";
20
22
 
21
23
  /**
@@ -31,6 +33,12 @@ export const RouterliciousErrorTypes = {
31
33
  * SSL Certificate Error.
32
34
  */
33
35
  sslCertError: "sslCertError",
36
+
37
+ /**
38
+ * Error for when the service drains a cluster to which the socket connection is connected to and it disconnects
39
+ * all the clients in that cluster.
40
+ */
41
+ clusterDrainingError: "clusterDrainingError",
34
42
  } as const;
35
43
  /**
36
44
  * @internal
@@ -66,6 +74,24 @@ export interface IR11sSocketError {
66
74
  * The client should wait this many milliseconds before retrying its request.
67
75
  */
68
76
  retryAfterMs?: number;
77
+
78
+ /**
79
+ * Optional internalErrorCode to specify the specific error code for the error within the main code above.
80
+ */
81
+ internalErrorCode?: string | number;
82
+ }
83
+
84
+ export class ClusterDrainingError extends LoggingError implements IFluidErrorBase {
85
+ readonly errorType = RouterliciousErrorTypes.clusterDrainingError;
86
+ readonly canRetry = true;
87
+
88
+ constructor(
89
+ message: string,
90
+ readonly retryAfterSeconds: number,
91
+ props: DriverErrorTelemetryProps,
92
+ ) {
93
+ super(message, props);
94
+ }
69
95
  }
70
96
 
71
97
  export interface IR11sError extends Omit<IDriverErrorBase, "errorType"> {
@@ -78,9 +104,11 @@ export function createR11sNetworkError(
78
104
  errorMessage: string,
79
105
  statusCode: number,
80
106
  retryAfterMs?: number,
107
+ additionalProps?: DriverErrorTelemetryProps,
108
+ internalErrorCode?: string | number,
81
109
  ): IFluidErrorBase & R11sError {
82
110
  let error: IFluidErrorBase & R11sError;
83
- const props = { statusCode, driverVersion };
111
+ const props = { ...additionalProps, statusCode, driverVersion };
84
112
  switch (statusCode) {
85
113
  case 401:
86
114
  // The first 401 is manually retried in RouterliciousRestWrapper with a refreshed token,
@@ -99,6 +127,15 @@ export function createR11sNetworkError(
99
127
  case 502:
100
128
  error = new GenericNetworkError(errorMessage, true, props);
101
129
  break;
130
+ case 503:
131
+ if (internalErrorCode === R11sServiceClusterDrainingErrorCode) {
132
+ error = new ClusterDrainingError(
133
+ errorMessage,
134
+ retryAfterMs !== undefined ? retryAfterMs / 1000 : 660,
135
+ props,
136
+ );
137
+ break;
138
+ }
102
139
  default:
103
140
  const retryInfo = { canRetry: retryAfterMs !== undefined, retryAfterMs };
104
141
  error = createGenericNetworkError(errorMessage, retryInfo, props);
@@ -112,10 +149,9 @@ export function throwR11sNetworkError(
112
149
  errorMessage: string,
113
150
  statusCode: number,
114
151
  retryAfterMs?: number,
152
+ additionalProps?: DriverErrorTelemetryProps,
115
153
  ): never {
116
- const networkError = createR11sNetworkError(errorMessage, statusCode, retryAfterMs);
117
-
118
- throw networkError;
154
+ throw createR11sNetworkError(errorMessage, statusCode, retryAfterMs, additionalProps);
119
155
  }
120
156
 
121
157
  /**
@@ -124,8 +160,69 @@ export function throwR11sNetworkError(
124
160
  export function errorObjectFromSocketError(
125
161
  socketError: IR11sSocketError,
126
162
  handler: string,
163
+ additionalProps?: DriverErrorTelemetryProps,
127
164
  ): R11sError {
128
165
  // pre-0.58 error message prefix: R11sSocketError
129
166
  const message = `R11s socket error (${handler}): ${socketError.message}`;
130
- return createR11sNetworkError(message, socketError.code, socketError.retryAfterMs);
167
+ const error = createR11sNetworkError(
168
+ message,
169
+ socketError.code,
170
+ socketError.retryAfterMs,
171
+ additionalProps,
172
+ socketError.internalErrorCode,
173
+ );
174
+ error.addTelemetryProperties({
175
+ relayServiceError: true,
176
+ scenarioName: handler,
177
+ internalErrorCode: socketError.internalErrorCode,
178
+ });
179
+ return error;
180
+ }
181
+
182
+ /** Simulate the pathname for socket connection */
183
+ export const socketIoPath = "socket.io";
184
+
185
+ /**
186
+ * Get a stripped version of a URL safe for r11s telemetry
187
+ * @returns undefined if no appropriate hostName is provided
188
+ */
189
+ export function getUrlForTelemetry(hostName: string, path: string = ""): string | undefined {
190
+ // Strip off "http://" or "https://"
191
+ const hostNameMatch = hostName.match(/^(?:https?:\/\/)?([^/]+)/);
192
+ if (!hostNameMatch) {
193
+ return undefined;
194
+ }
195
+ const strippedHostName = hostNameMatch[1];
196
+
197
+ let extractedPath = "";
198
+ // Extract portions of the path and explicitly match them to known path names
199
+ for (const portion of path.split("/")) {
200
+ if (portion !== "") {
201
+ // eslint-disable-next-line unicorn/prefer-ternary
202
+ if (
203
+ [
204
+ socketIoPath,
205
+ "repos",
206
+ "deltas",
207
+ "documents",
208
+ "session",
209
+ "git",
210
+ "summaries",
211
+ "latest",
212
+ "document",
213
+ "commits",
214
+ "blobs",
215
+ "refs",
216
+ "revokeToken",
217
+ "accesstoken",
218
+ ].includes(portion)
219
+ ) {
220
+ extractedPath += `/${portion}`;
221
+ } else {
222
+ extractedPath += "/REDACTED";
223
+ }
224
+ }
225
+ }
226
+
227
+ return `${strippedHostName}${extractedPath}`;
131
228
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/routerlicious-driver";
9
- export const pkgVersion = "2.5.0-302463";
9
+ export const pkgVersion = "2.5.0";
@@ -26,7 +26,11 @@ import fetch from "cross-fetch";
26
26
  import safeStringify from "json-stringify-safe";
27
27
 
28
28
  import type { AxiosRequestConfig, RawAxiosRequestHeaders } from "./axios.cjs";
29
- import { RouterliciousErrorTypes, throwR11sNetworkError } from "./errorUtils.js";
29
+ import {
30
+ getUrlForTelemetry,
31
+ RouterliciousErrorTypes,
32
+ throwR11sNetworkError,
33
+ } from "./errorUtils.js";
30
34
  import { pkgVersion as driverVersion } from "./packageVersion.js";
31
35
  import { addOrUpdateQueryParams, type QueryStringType } from "./queryStringUtils.js";
32
36
  import { RestWrapper } from "./restWrapperBase.js";
@@ -174,6 +178,13 @@ class RouterliciousRestWrapper extends RestWrapper {
174
178
  // on failure, add the request entry into the retryCounter map to count the subsequent retries, if any
175
179
  this.retryCounter.set(requestKey, requestRetryCount ? requestRetryCount + 1 : 1);
176
180
 
181
+ const telemetryProps = {
182
+ driverVersion,
183
+ retryCount: requestRetryCount,
184
+ url: getUrlForTelemetry(completeRequestUrl.hostname, completeRequestUrl.pathname),
185
+ requestMethod: fetchRequestConfig.method,
186
+ };
187
+
177
188
  // Browser Fetch throws a TypeError on network error, `node-fetch` throws a FetchError
178
189
  const isNetworkError = ["TypeError", "FetchError"].includes(error?.name);
179
190
  const errorMessage = isNetworkError
@@ -185,14 +196,16 @@ class RouterliciousRestWrapper extends RestWrapper {
185
196
  // If there exists a self-signed SSL certificates error, throw a NonRetryableError
186
197
  // TODO: instead of relying on string matching, filter error based on the error code like we do for websocket connections
187
198
  const err = errorMessage.includes("failed, reason: self signed certificate")
188
- ? new NonRetryableError(errorMessage, RouterliciousErrorTypes.sslCertError, {
189
- driverVersion,
190
- retryCount: requestRetryCount,
191
- })
192
- : new GenericNetworkError(errorMessage, errorMessage.startsWith("NetworkError"), {
193
- driverVersion,
194
- retryCount: requestRetryCount,
195
- });
199
+ ? new NonRetryableError(
200
+ errorMessage,
201
+ RouterliciousErrorTypes.sslCertError,
202
+ telemetryProps,
203
+ )
204
+ : new GenericNetworkError(
205
+ errorMessage,
206
+ errorMessage.startsWith("NetworkError"),
207
+ telemetryProps,
208
+ );
196
209
  throw err;
197
210
  },
198
211
  );
@@ -203,6 +216,7 @@ class RouterliciousRestWrapper extends RestWrapper {
203
216
  });
204
217
 
205
218
  const response = res.response;
219
+ const headers = headersToMap(response.headers);
206
220
 
207
221
  let start = performance.now();
208
222
  const text = await response.text();
@@ -222,7 +236,6 @@ class RouterliciousRestWrapper extends RestWrapper {
222
236
  // on successful response, remove the entry from the retryCounter map
223
237
  this.retryCounter.delete(requestKey);
224
238
  const result = responseBody as T;
225
- const headers = headersToMap(response.headers);
226
239
  return {
227
240
  content: result,
228
241
  headers,
@@ -268,6 +281,12 @@ class RouterliciousRestWrapper extends RestWrapper {
268
281
  `R11s fetch error: ${responseSummary}`,
269
282
  response.status,
270
283
  responseBody?.retryAfter,
284
+ {
285
+ ...getPropsToLogFromResponse(headers),
286
+ driverVersion,
287
+ url: getUrlForTelemetry(completeRequestUrl.hostname, completeRequestUrl.pathname),
288
+ requestMethod: fetchRequestConfig.method,
289
+ },
271
290
  );
272
291
  }
273
292
 
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { io } from "socket.io-client";
7
+
8
+ // Import is required for side-effects.
9
+ export const SocketIOClientStatic = io;