@fluidframework/tool-utils 2.90.0-378676 → 2.91.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.
@@ -11,7 +11,6 @@ import type {
11
11
  } from "@fluidframework/odsp-doclib-utils/internal";
12
12
  import {
13
13
  fetchTokens,
14
- getLoginPageUrl,
15
14
  getOdspScope,
16
15
  pushScope,
17
16
  refreshTokens,
@@ -21,11 +20,6 @@ import { Mutex } from "async-mutex";
21
20
  import { debug } from "./debug.js";
22
21
  import type { IAsyncCache, IResources } from "./fluidToolRc.js";
23
22
  import { loadRC, lockRC, saveRC } from "./fluidToolRc.js";
24
- import { endResponse, serverListenAndHandle } from "./httpHelpers.js";
25
-
26
- const odspAuthRedirectPort = 7000;
27
- const odspAuthRedirectOrigin = `http://localhost:${odspAuthRedirectPort}`;
28
- const odspAuthRedirectUri = new URL("/auth/callback", odspAuthRedirectOrigin).href;
29
23
 
30
24
  // TODO: Add documentation
31
25
  // eslint-disable-next-line jsdoc/require-description
@@ -45,16 +39,16 @@ export const getMicrosoftConfiguration = (): IPublicClientConfig => ({
45
39
  /**
46
40
  * @internal
47
41
  */
48
- export type OdspTokenConfig =
42
+ export type LoginCredentials =
49
43
  | {
50
44
  type: "password";
51
45
  username: string;
52
46
  password: string;
53
47
  }
54
48
  | {
55
- type: "browserLogin";
56
- navigator: (url: string) => void;
57
- redirectUriCallback?: (tokens: IOdspTokens) => Promise<string>;
49
+ type: "fic";
50
+ username: string;
51
+ fetchToken(scopeEndpoint: "push" | "storage"): Promise<string>;
58
52
  };
59
53
 
60
54
  /**
@@ -62,7 +56,7 @@ export type OdspTokenConfig =
62
56
  */
63
57
  export interface IOdspTokenManagerCacheKey {
64
58
  readonly isPush: boolean;
65
- readonly userOrServer: string;
59
+ readonly user: string;
66
60
  }
67
61
 
68
62
  const isValidAndNotExpiredToken = (tokens: IOdspTokens): boolean => {
@@ -82,7 +76,7 @@ const isValidAndNotExpiredToken = (tokens: IOdspTokens): boolean => {
82
76
  };
83
77
 
84
78
  const cacheKeyToString = (key: IOdspTokenManagerCacheKey): string => {
85
- return `${key.userOrServer}${key.isPush ? "[Push]" : ""}`;
79
+ return `${key.user}${key.isPush ? "[Push]" : ""}`;
86
80
  };
87
81
 
88
82
  /**
@@ -111,37 +105,37 @@ export class OdspTokenManager {
111
105
  ): Promise<void> {
112
106
  debug(`${cacheKeyToString(key)}: Saving tokens`);
113
107
  const memoryCache = key.isPush ? this.pushCache : this.storageCache;
114
- memoryCache.set(key.userOrServer, value);
108
+ memoryCache.set(key.user, value);
115
109
  await this.tokenCache?.save(key, value);
116
110
  }
117
111
 
118
112
  public async getOdspTokens(
119
113
  server: string,
120
114
  clientConfig: IPublicClientConfig,
121
- tokenConfig: OdspTokenConfig,
115
+ credentials: LoginCredentials,
122
116
  forceRefresh = false,
123
117
  forceReauth = false,
124
118
  ): Promise<IOdspTokens> {
125
119
  debug("Getting odsp tokens");
126
- return this.getTokens(false, server, clientConfig, tokenConfig, forceRefresh, forceReauth);
120
+ return this.getTokens(false, server, clientConfig, credentials, forceRefresh, forceReauth);
127
121
  }
128
122
 
129
123
  public async getPushTokens(
130
124
  server: string,
131
125
  clientConfig: IPublicClientConfig,
132
- tokenConfig: OdspTokenConfig,
126
+ credentials: LoginCredentials,
133
127
  forceRefresh = false,
134
128
  forceReauth = false,
135
129
  ): Promise<IOdspTokens> {
136
130
  debug("Getting push tokens");
137
- return this.getTokens(true, server, clientConfig, tokenConfig, forceRefresh, forceReauth);
131
+ return this.getTokens(true, server, clientConfig, credentials, forceRefresh, forceReauth);
138
132
  }
139
133
 
140
134
  private async getTokenFromCache(
141
135
  cacheKey: IOdspTokenManagerCacheKey,
142
136
  ): Promise<IOdspTokens | undefined> {
143
137
  const memoryCache = cacheKey.isPush ? this.pushCache : this.storageCache;
144
- const memoryToken = memoryCache.get(cacheKey.userOrServer);
138
+ const memoryToken = memoryCache.get(cacheKey.user);
145
139
  if (memoryToken) {
146
140
  debug(`${cacheKeyToString(cacheKey)}: Token found in memory `);
147
141
  return memoryToken;
@@ -149,20 +143,18 @@ export class OdspTokenManager {
149
143
  const fileToken = await this.tokenCache?.get(cacheKey);
150
144
  if (fileToken) {
151
145
  debug(`${cacheKeyToString(cacheKey)}: Token found in file`);
152
- memoryCache.set(cacheKey.userOrServer, fileToken);
146
+ memoryCache.set(cacheKey.user, fileToken);
153
147
  return fileToken;
154
148
  }
155
149
  }
156
150
 
157
151
  private static getCacheKey(
158
152
  isPush: boolean,
159
- tokenConfig: OdspTokenConfig,
160
- server: string,
153
+ credentials: LoginCredentials,
161
154
  ): IOdspTokenManagerCacheKey {
162
- // If we are using password, we should cache the token per user instead of per server
163
155
  return {
164
156
  isPush,
165
- userOrServer: tokenConfig.type === "password" ? tokenConfig.username : server,
157
+ user: credentials.username,
166
158
  };
167
159
  }
168
160
 
@@ -170,7 +162,7 @@ export class OdspTokenManager {
170
162
  isPush: boolean,
171
163
  server: string,
172
164
  clientConfig: IPublicClientConfig,
173
- tokenConfig: OdspTokenConfig,
165
+ credentials: LoginCredentials,
174
166
  forceRefresh: boolean,
175
167
  forceReauth: boolean,
176
168
  ): Promise<IOdspTokens> {
@@ -182,7 +174,7 @@ export class OdspTokenManager {
182
174
  isPush,
183
175
  server,
184
176
  clientConfig,
185
- tokenConfig,
177
+ credentials,
186
178
  forceRefresh,
187
179
  forceReauth,
188
180
  );
@@ -190,12 +182,11 @@ export class OdspTokenManager {
190
182
  };
191
183
  if (!forceReauth && !forceRefresh) {
192
184
  // check and return if it exists without lock
193
- const cacheKey = OdspTokenManager.getCacheKey(isPush, tokenConfig, server);
185
+ const cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);
194
186
  const tokensFromCache = await this.getTokenFromCache(cacheKey);
195
187
  if (tokensFromCache) {
196
188
  if (isValidAndNotExpiredToken(tokensFromCache)) {
197
189
  debug(`${cacheKeyToString(cacheKey)}: Token reused from cache `);
198
- await this.onTokenRetrievalFromCache(tokenConfig, tokensFromCache);
199
190
  return tokensFromCache;
200
191
  }
201
192
  debug(`${cacheKeyToString(cacheKey)}: Token expired from cache `);
@@ -212,12 +203,12 @@ export class OdspTokenManager {
212
203
  isPush: boolean,
213
204
  server: string,
214
205
  clientConfig: IPublicClientConfig,
215
- tokenConfig: OdspTokenConfig,
206
+ credentials: LoginCredentials,
216
207
  forceRefresh: boolean,
217
208
  forceReauth: boolean,
218
209
  ): Promise<IOdspTokens> {
219
210
  const scope = isPush ? pushScope : getOdspScope(server);
220
- const cacheKey = OdspTokenManager.getCacheKey(isPush, tokenConfig, server);
211
+ const cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);
221
212
  let tokens: IOdspTokens | undefined;
222
213
  if (!forceReauth) {
223
214
  // check the cache again under the lock (if it is there)
@@ -225,8 +216,16 @@ export class OdspTokenManager {
225
216
  if (tokensFromCache) {
226
217
  if (forceRefresh || !isValidAndNotExpiredToken(tokensFromCache)) {
227
218
  try {
228
- // This updates the tokens in tokensFromCache
229
- tokens = await refreshTokens(server, scope, clientConfig, tokensFromCache);
219
+ if (credentials.type === "fic") {
220
+ const scopeEndpoint = isPush ? "push" : "storage";
221
+ const newTokenData = await credentials.fetchToken(scopeEndpoint);
222
+ tokens = this.ficTokenToIOdspTokens(newTokenData, isPush);
223
+ } else if (credentials.type === "password") {
224
+ // For OAuth flows, use refresh token
225
+ tokens = await refreshTokens(server, scope, clientConfig, tokensFromCache);
226
+ } else {
227
+ unreachableCase(credentials);
228
+ }
230
229
  await this.updateTokensCacheWithoutLock(cacheKey, tokens);
231
230
  } catch (error) {
232
231
  debug(`${cacheKeyToString(cacheKey)}: Error in refreshing token. ${error}`);
@@ -236,37 +235,29 @@ export class OdspTokenManager {
236
235
  debug(`${cacheKeyToString(cacheKey)}: Token reused from locked cache `);
237
236
  }
238
237
  }
238
+ if (tokens) {
239
+ return tokens;
240
+ }
239
241
  }
240
242
 
241
- if (tokens) {
242
- await this.onTokenRetrievalFromCache(tokenConfig, tokens);
243
- return tokens;
244
- }
245
-
246
- switch (tokenConfig.type) {
243
+ switch (credentials.type) {
247
244
  case "password": {
248
245
  tokens = await this.acquireTokensWithPassword(
249
246
  server,
250
247
  scope,
251
248
  clientConfig,
252
- tokenConfig.username,
253
- tokenConfig.password,
249
+ credentials.username,
250
+ credentials.password,
254
251
  );
255
252
  break;
256
253
  }
257
- case "browserLogin": {
258
- tokens = await this.acquireTokensViaBrowserLogin(
259
- getLoginPageUrl(server, clientConfig, scope, odspAuthRedirectUri),
260
- server,
261
- clientConfig,
262
- scope,
263
- tokenConfig.navigator,
264
- tokenConfig.redirectUriCallback,
265
- );
254
+ case "fic": {
255
+ const tokenData = await credentials.fetchToken(isPush ? "push" : "storage");
256
+ tokens = this.ficTokenToIOdspTokens(tokenData, isPush);
266
257
  break;
267
258
  }
268
259
  default: {
269
- unreachableCase(tokenConfig);
260
+ unreachableCase(credentials);
270
261
  }
271
262
  }
272
263
 
@@ -296,64 +287,50 @@ export class OdspTokenManager {
296
287
  return fetchTokens(server, scope, clientConfig, credentials);
297
288
  }
298
289
 
299
- private async acquireTokensViaBrowserLogin(
300
- loginPageUrl: string,
301
- server: string,
302
- clientConfig: IPublicClientConfig,
303
- scope: string,
304
- navigator: (url: string) => void,
305
- redirectUriCallback?: (tokens: IOdspTokens) => Promise<string>,
306
- ): Promise<IOdspTokens> {
307
- // Start up a local auth redirect handler service to receive the tokens after login
308
- const tokenGetter = await serverListenAndHandle(odspAuthRedirectPort, async (req, res) => {
309
- // extract code from request URL and fetch the tokens
310
- const credentials: TokenRequestCredentials = {
311
- grant_type: "authorization_code",
312
- code: this.extractAuthorizationCode(req.url),
313
- redirect_uri: odspAuthRedirectUri,
290
+ private ficTokenToIOdspTokens(token: string, isPush: boolean): IOdspTokens {
291
+ // eslint-disable-next-line unicorn/prefer-ternary -- using if statement for clarity
292
+ if (isPush) {
293
+ // Push tokens are not standard JWTs. With direct token exchange, the second leg includes information about expiry.
294
+ // This is not available in the FIC flow, but in direct token exchange we request tokens with 1 hour expiry so default to that.
295
+ // At worst this should result in some higher latency when a token is returned from the cache when it should really be
296
+ // refreshed immediately (as attempting to use such a token will trigger a token refresh flow indirectly).
297
+ return {
298
+ accessToken: token,
299
+ receivedAt: Math.floor(Date.now() / 1000),
300
+ expiresIn: 3600,
314
301
  };
315
- const tokens = await fetchTokens(server, scope, clientConfig, credentials);
316
-
317
- // redirect now that the browser is done with auth
318
- if (redirectUriCallback) {
319
- res.writeHead(301, { Location: await redirectUriCallback(tokens) });
320
- await endResponse(res);
321
- } else {
322
- res.write("Please close the window");
323
- await endResponse(res);
324
- }
325
-
326
- return tokens;
327
- });
328
-
329
- // Now that our local redirect handler is up, navigate the browser to the login page
330
- navigator(loginPageUrl);
331
-
332
- // Receive and extract the tokens
333
- const odspTokens = await tokenGetter();
334
-
335
- return odspTokens;
336
- }
337
-
338
- private async onTokenRetrievalFromCache(
339
- config: OdspTokenConfig,
340
- tokens: IOdspTokens,
341
- ): Promise<void> {
342
- if (config.type === "browserLogin" && config.redirectUriCallback) {
343
- config.navigator(await config.redirectUriCallback(tokens));
302
+ } else {
303
+ return this.jwtToIOdspTokens(token);
344
304
  }
345
305
  }
346
306
 
347
- private extractAuthorizationCode(relativeUrl: string | undefined): string {
348
- if (relativeUrl === undefined) {
349
- throw new Error("Failed to get authorization");
307
+ private jwtToIOdspTokens(token: string): IOdspTokens {
308
+ let receivedAt: number;
309
+ let expiresIn: number;
310
+ const payloadSegment = token.split(".")[1];
311
+ if (payloadSegment === undefined) {
312
+ throw new Error("Invalid JWT format");
313
+ }
314
+ const payload = JSON.parse(Buffer.from(payloadSegment, "base64url").toString("utf8")) as {
315
+ iat?: number;
316
+ exp?: number;
317
+ };
318
+ if (typeof payload.iat === "number") {
319
+ receivedAt = payload.iat;
320
+ } else {
321
+ throw new TypeError("JWT payload lacks valid iat claim.");
350
322
  }
351
- const parsedUrl = new URL(relativeUrl, odspAuthRedirectOrigin);
352
- const code = parsedUrl.searchParams.get("code");
353
- if (code === null || code === undefined) {
354
- throw new Error("Failed to get authorization");
323
+ if (typeof payload.exp === "number" && typeof payload.iat === "number") {
324
+ expiresIn = payload.exp - payload.iat;
325
+ } else {
326
+ throw new TypeError("JWT payload lacks valid exp claim.");
355
327
  }
356
- return code;
328
+
329
+ return {
330
+ accessToken: token,
331
+ receivedAt,
332
+ expiresIn,
333
+ };
357
334
  }
358
335
  }
359
336
 
@@ -374,7 +351,7 @@ async function loadAndPatchRC(): Promise<IResources> {
374
351
  export const odspTokensCache: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens> = {
375
352
  async get(key: IOdspTokenManagerCacheKey): Promise<IOdspTokens | undefined> {
376
353
  const rc = await loadAndPatchRC();
377
- return rc.tokens?.data[key.userOrServer]?.[key.isPush ? "push" : "storage"];
354
+ return rc.tokens?.data[key.user]?.[key.isPush ? "push" : "storage"];
378
355
  },
379
356
  async save(key: IOdspTokenManagerCacheKey, tokens: IOdspTokens): Promise<void> {
380
357
  const rc = await loadAndPatchRC();
@@ -385,10 +362,10 @@ export const odspTokensCache: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens
385
362
  data: {},
386
363
  };
387
364
  }
388
- let prevTokens = rc.tokens.data[key.userOrServer];
365
+ let prevTokens = rc.tokens.data[key.user];
389
366
  if (!prevTokens) {
390
367
  prevTokens = {};
391
- rc.tokens.data[key.userOrServer] = prevTokens;
368
+ rc.tokens.data[key.user] = prevTokens;
392
369
  }
393
370
  prevTokens[key.isPush ? "push" : "storage"] = tokens;
394
371
  return saveRC(rc);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/tool-utils";
9
- export const pkgVersion = "2.90.0-378676";
9
+ export const pkgVersion = "2.91.0";
@@ -1,19 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- /// <reference types="node" />
6
- /// <reference types="node" />
7
- import http from "node:http";
8
- import type { Socket } from "node:net";
9
- export interface ITrackedHttpServer {
10
- readonly server: http.Server;
11
- readonly sockets: Set<Socket>;
12
- fullyClose(): void;
13
- }
14
- export declare function createTrackedServer(port: number, requestListener: http.RequestListener): ITrackedHttpServer;
15
- export type OnceListenerHandler<T> = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<T>;
16
- export type OnceListenerResult<T> = Promise<() => Promise<T>>;
17
- export declare const serverListenAndHandle: <T>(port: number, handler: OnceListenerHandler<T>) => OnceListenerResult<T>;
18
- export declare const endResponse: (response: http.ServerResponse) => Promise<void>;
19
- //# sourceMappingURL=httpHelpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpHelpers.d.ts","sourceRoot":"","sources":["../src/httpHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;;;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIvC,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,UAAU,IAAI,IAAI,CAAC;CACnB;AAID,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,IAAI,CAAC,eAAe,GACnC,kBAAkB,CAmBpB;AAID,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,CACpC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,KACpB,OAAO,CAAC,CAAC,CAAC,CAAC;AAIhB,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAI9D,eAAO,MAAM,qBAAqB,YAC3B,MAAM,WACH,oBAAoB,CAAC,CAAC,KAC7B,mBAAmB,CAAC,CAqBpB,CAAC;AAIJ,eAAO,MAAM,WAAW,aAAoB,KAAK,cAAc,KAAG,QAAQ,IAAI,CAQ3E,CAAC"}
@@ -1,68 +0,0 @@
1
- "use strict";
2
- /*!
3
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
4
- * Licensed under the MIT License.
5
- */
6
- var __importDefault = (this && this.__importDefault) || function (mod) {
7
- return (mod && mod.__esModule) ? mod : { "default": mod };
8
- };
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.endResponse = exports.serverListenAndHandle = exports.createTrackedServer = void 0;
11
- const node_http_1 = __importDefault(require("node:http"));
12
- // TODO: Add documentation
13
- // eslint-disable-next-line jsdoc/require-jsdoc
14
- function createTrackedServer(port, requestListener) {
15
- const server = node_http_1.default.createServer(requestListener).listen(port);
16
- const sockets = new Set();
17
- server.on("connection", (socket) => {
18
- sockets.add(socket);
19
- socket.on("close", () => sockets.delete(socket));
20
- });
21
- return {
22
- server,
23
- sockets,
24
- fullyClose() {
25
- server.close();
26
- for (const socket of sockets) {
27
- socket.destroy();
28
- }
29
- },
30
- };
31
- }
32
- exports.createTrackedServer = createTrackedServer;
33
- // TODO: Add documentation
34
- // eslint-disable-next-line jsdoc/require-jsdoc
35
- const serverListenAndHandle = async (port, handler) =>
36
- // eslint-disable-next-line promise/param-names
37
- new Promise((outerResolve, outerReject) => {
38
- const innerP = new Promise((innerResolve, innerReject) => {
39
- const httpServer = createTrackedServer(port, (req, res) => {
40
- // ignore favicon
41
- if (req.url === "/favicon.ico") {
42
- res.writeHead(200, { "Content-Type": "image/x-icon" });
43
- res.end();
44
- return;
45
- }
46
- handler(req, res)
47
- .finally(() => httpServer.fullyClose())
48
- .then((result) => innerResolve(result),
49
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
50
- (error) => innerReject(error));
51
- });
52
- outerResolve(async () => innerP);
53
- });
54
- });
55
- exports.serverListenAndHandle = serverListenAndHandle;
56
- // TODO: Add documentation
57
- // eslint-disable-next-line jsdoc/require-jsdoc
58
- const endResponse = async (response) => new Promise((resolve, reject) => {
59
- try {
60
- response.end(resolve);
61
- }
62
- catch (error) {
63
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
64
- reject(error);
65
- }
66
- });
67
- exports.endResponse = endResponse;
68
- //# sourceMappingURL=httpHelpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpHelpers.js","sourceRoot":"","sources":["../src/httpHelpers.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,0DAA6B;AAW7B,0BAA0B;AAC1B,+CAA+C;AAC/C,SAAgB,mBAAmB,CAClC,IAAY,EACZ,eAAqC;IAErC,MAAM,MAAM,GAAG,mBAAI,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;QAClC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,OAAO;QACN,MAAM;QACN,OAAO;QACP,UAAU;YACT,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,MAAM,CAAC,OAAO,EAAE,CAAC;YAClB,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC;AAtBD,kDAsBC;AAaD,0BAA0B;AAC1B,+CAA+C;AACxC,MAAM,qBAAqB,GAAG,KAAK,EACzC,IAAY,EACZ,OAA+B,EACP,EAAE;AAC1B,+CAA+C;AAC/C,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE;IACzC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAI,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE;QAC3D,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACzD,iBAAiB;YACjB,IAAI,GAAG,CAAC,GAAG,KAAK,cAAc,EAAE,CAAC;gBAChC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC,CAAC;gBACvD,GAAG,CAAC,GAAG,EAAE,CAAC;gBACV,OAAO;YACR,CAAC;YACD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC;iBACf,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;iBACtC,IAAI,CACJ,CAAC,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC;YAChC,2EAA2E;YAC3E,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAC7B,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAxBS,QAAA,qBAAqB,yBAwB9B;AAEJ,0BAA0B;AAC1B,+CAA+C;AACxC,MAAM,WAAW,GAAG,KAAK,EAAE,QAA6B,EAAiB,EAAE,CACjF,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC/B,IAAI,CAAC;QACJ,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,2EAA2E;QAC3E,MAAM,CAAC,KAAK,CAAC,CAAC;IACf,CAAC;AACF,CAAC,CAAC,CAAC;AARS,QAAA,WAAW,eAQpB","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport http from \"node:http\";\nimport type { Socket } from \"node:net\";\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport interface ITrackedHttpServer {\n\treadonly server: http.Server;\n\treadonly sockets: Set<Socket>;\n\tfullyClose(): void;\n}\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport function createTrackedServer(\n\tport: number,\n\trequestListener: http.RequestListener,\n): ITrackedHttpServer {\n\tconst server = http.createServer(requestListener).listen(port);\n\tconst sockets = new Set<Socket>();\n\n\tserver.on(\"connection\", (socket) => {\n\t\tsockets.add(socket);\n\t\tsocket.on(\"close\", () => sockets.delete(socket));\n\t});\n\n\treturn {\n\t\tserver,\n\t\tsockets,\n\t\tfullyClose(): void {\n\t\t\tserver.close();\n\t\t\tfor (const socket of sockets) {\n\t\t\t\tsocket.destroy();\n\t\t\t}\n\t\t},\n\t};\n}\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport type OnceListenerHandler<T> = (\n\treq: http.IncomingMessage,\n\tres: http.ServerResponse,\n) => Promise<T>;\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport type OnceListenerResult<T> = Promise<() => Promise<T>>;\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport const serverListenAndHandle = async <T>(\n\tport: number,\n\thandler: OnceListenerHandler<T>,\n): OnceListenerResult<T> =>\n\t// eslint-disable-next-line promise/param-names\n\tnew Promise((outerResolve, outerReject) => {\n\t\tconst innerP = new Promise<T>((innerResolve, innerReject) => {\n\t\t\tconst httpServer = createTrackedServer(port, (req, res) => {\n\t\t\t\t// ignore favicon\n\t\t\t\tif (req.url === \"/favicon.ico\") {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"image/x-icon\" });\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\thandler(req, res)\n\t\t\t\t\t.finally(() => httpServer.fullyClose())\n\t\t\t\t\t.then(\n\t\t\t\t\t\t(result) => innerResolve(result),\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n\t\t\t\t\t\t(error) => innerReject(error),\n\t\t\t\t\t);\n\t\t\t});\n\t\t\touterResolve(async () => innerP);\n\t\t});\n\t});\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport const endResponse = async (response: http.ServerResponse): Promise<void> =>\n\tnew Promise((resolve, reject) => {\n\t\ttry {\n\t\t\tresponse.end(resolve);\n\t\t} catch (error) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n\t\t\treject(error);\n\t\t}\n\t});\n"]}
@@ -1,19 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- /// <reference types="node" resolution-mode="require"/>
6
- /// <reference types="node" resolution-mode="require"/>
7
- import http from "node:http";
8
- import type { Socket } from "node:net";
9
- export interface ITrackedHttpServer {
10
- readonly server: http.Server;
11
- readonly sockets: Set<Socket>;
12
- fullyClose(): void;
13
- }
14
- export declare function createTrackedServer(port: number, requestListener: http.RequestListener): ITrackedHttpServer;
15
- export type OnceListenerHandler<T> = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<T>;
16
- export type OnceListenerResult<T> = Promise<() => Promise<T>>;
17
- export declare const serverListenAndHandle: <T>(port: number, handler: OnceListenerHandler<T>) => OnceListenerResult<T>;
18
- export declare const endResponse: (response: http.ServerResponse) => Promise<void>;
19
- //# sourceMappingURL=httpHelpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpHelpers.d.ts","sourceRoot":"","sources":["../src/httpHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;;;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIvC,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,UAAU,IAAI,IAAI,CAAC;CACnB;AAID,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,IAAI,CAAC,eAAe,GACnC,kBAAkB,CAmBpB;AAID,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,CACpC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,KACpB,OAAO,CAAC,CAAC,CAAC,CAAC;AAIhB,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAI9D,eAAO,MAAM,qBAAqB,YAC3B,MAAM,WACH,oBAAoB,CAAC,CAAC,KAC7B,mBAAmB,CAAC,CAqBpB,CAAC;AAIJ,eAAO,MAAM,WAAW,aAAoB,KAAK,cAAc,KAAG,QAAQ,IAAI,CAQ3E,CAAC"}
@@ -1,59 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import http from "node:http";
6
- // TODO: Add documentation
7
- // eslint-disable-next-line jsdoc/require-jsdoc
8
- export function createTrackedServer(port, requestListener) {
9
- const server = http.createServer(requestListener).listen(port);
10
- const sockets = new Set();
11
- server.on("connection", (socket) => {
12
- sockets.add(socket);
13
- socket.on("close", () => sockets.delete(socket));
14
- });
15
- return {
16
- server,
17
- sockets,
18
- fullyClose() {
19
- server.close();
20
- for (const socket of sockets) {
21
- socket.destroy();
22
- }
23
- },
24
- };
25
- }
26
- // TODO: Add documentation
27
- // eslint-disable-next-line jsdoc/require-jsdoc
28
- export const serverListenAndHandle = async (port, handler) =>
29
- // eslint-disable-next-line promise/param-names
30
- new Promise((outerResolve, outerReject) => {
31
- const innerP = new Promise((innerResolve, innerReject) => {
32
- const httpServer = createTrackedServer(port, (req, res) => {
33
- // ignore favicon
34
- if (req.url === "/favicon.ico") {
35
- res.writeHead(200, { "Content-Type": "image/x-icon" });
36
- res.end();
37
- return;
38
- }
39
- handler(req, res)
40
- .finally(() => httpServer.fullyClose())
41
- .then((result) => innerResolve(result),
42
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
43
- (error) => innerReject(error));
44
- });
45
- outerResolve(async () => innerP);
46
- });
47
- });
48
- // TODO: Add documentation
49
- // eslint-disable-next-line jsdoc/require-jsdoc
50
- export const endResponse = async (response) => new Promise((resolve, reject) => {
51
- try {
52
- response.end(resolve);
53
- }
54
- catch (error) {
55
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
56
- reject(error);
57
- }
58
- });
59
- //# sourceMappingURL=httpHelpers.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpHelpers.js","sourceRoot":"","sources":["../src/httpHelpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAW7B,0BAA0B;AAC1B,+CAA+C;AAC/C,MAAM,UAAU,mBAAmB,CAClC,IAAY,EACZ,eAAqC;IAErC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;QAClC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,OAAO;QACN,MAAM;QACN,OAAO;QACP,UAAU;YACT,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,MAAM,CAAC,OAAO,EAAE,CAAC;YAClB,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC;AAaD,0BAA0B;AAC1B,+CAA+C;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EACzC,IAAY,EACZ,OAA+B,EACP,EAAE;AAC1B,+CAA+C;AAC/C,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE;IACzC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAI,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE;QAC3D,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACzD,iBAAiB;YACjB,IAAI,GAAG,CAAC,GAAG,KAAK,cAAc,EAAE,CAAC;gBAChC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC,CAAC;gBACvD,GAAG,CAAC,GAAG,EAAE,CAAC;gBACV,OAAO;YACR,CAAC;YACD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC;iBACf,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;iBACtC,IAAI,CACJ,CAAC,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC;YAChC,2EAA2E;YAC3E,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAC7B,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,YAAY,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC;AAEJ,0BAA0B;AAC1B,+CAA+C;AAC/C,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,QAA6B,EAAiB,EAAE,CACjF,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC/B,IAAI,CAAC;QACJ,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,2EAA2E;QAC3E,MAAM,CAAC,KAAK,CAAC,CAAC;IACf,CAAC;AACF,CAAC,CAAC,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport http from \"node:http\";\nimport type { Socket } from \"node:net\";\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport interface ITrackedHttpServer {\n\treadonly server: http.Server;\n\treadonly sockets: Set<Socket>;\n\tfullyClose(): void;\n}\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport function createTrackedServer(\n\tport: number,\n\trequestListener: http.RequestListener,\n): ITrackedHttpServer {\n\tconst server = http.createServer(requestListener).listen(port);\n\tconst sockets = new Set<Socket>();\n\n\tserver.on(\"connection\", (socket) => {\n\t\tsockets.add(socket);\n\t\tsocket.on(\"close\", () => sockets.delete(socket));\n\t});\n\n\treturn {\n\t\tserver,\n\t\tsockets,\n\t\tfullyClose(): void {\n\t\t\tserver.close();\n\t\t\tfor (const socket of sockets) {\n\t\t\t\tsocket.destroy();\n\t\t\t}\n\t\t},\n\t};\n}\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport type OnceListenerHandler<T> = (\n\treq: http.IncomingMessage,\n\tres: http.ServerResponse,\n) => Promise<T>;\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport type OnceListenerResult<T> = Promise<() => Promise<T>>;\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport const serverListenAndHandle = async <T>(\n\tport: number,\n\thandler: OnceListenerHandler<T>,\n): OnceListenerResult<T> =>\n\t// eslint-disable-next-line promise/param-names\n\tnew Promise((outerResolve, outerReject) => {\n\t\tconst innerP = new Promise<T>((innerResolve, innerReject) => {\n\t\t\tconst httpServer = createTrackedServer(port, (req, res) => {\n\t\t\t\t// ignore favicon\n\t\t\t\tif (req.url === \"/favicon.ico\") {\n\t\t\t\t\tres.writeHead(200, { \"Content-Type\": \"image/x-icon\" });\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\thandler(req, res)\n\t\t\t\t\t.finally(() => httpServer.fullyClose())\n\t\t\t\t\t.then(\n\t\t\t\t\t\t(result) => innerResolve(result),\n\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n\t\t\t\t\t\t(error) => innerReject(error),\n\t\t\t\t\t);\n\t\t\t});\n\t\t\touterResolve(async () => innerP);\n\t\t});\n\t});\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport const endResponse = async (response: http.ServerResponse): Promise<void> =>\n\tnew Promise((resolve, reject) => {\n\t\ttry {\n\t\t\tresponse.end(resolve);\n\t\t} catch (error) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n\t\t\treject(error);\n\t\t}\n\t});\n"]}
@@ -1,92 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
-
6
- import http from "node:http";
7
- import type { Socket } from "node:net";
8
-
9
- // TODO: Add documentation
10
- // eslint-disable-next-line jsdoc/require-jsdoc
11
- export interface ITrackedHttpServer {
12
- readonly server: http.Server;
13
- readonly sockets: Set<Socket>;
14
- fullyClose(): void;
15
- }
16
-
17
- // TODO: Add documentation
18
- // eslint-disable-next-line jsdoc/require-jsdoc
19
- export function createTrackedServer(
20
- port: number,
21
- requestListener: http.RequestListener,
22
- ): ITrackedHttpServer {
23
- const server = http.createServer(requestListener).listen(port);
24
- const sockets = new Set<Socket>();
25
-
26
- server.on("connection", (socket) => {
27
- sockets.add(socket);
28
- socket.on("close", () => sockets.delete(socket));
29
- });
30
-
31
- return {
32
- server,
33
- sockets,
34
- fullyClose(): void {
35
- server.close();
36
- for (const socket of sockets) {
37
- socket.destroy();
38
- }
39
- },
40
- };
41
- }
42
-
43
- // TODO: Add documentation
44
- // eslint-disable-next-line jsdoc/require-jsdoc
45
- export type OnceListenerHandler<T> = (
46
- req: http.IncomingMessage,
47
- res: http.ServerResponse,
48
- ) => Promise<T>;
49
-
50
- // TODO: Add documentation
51
- // eslint-disable-next-line jsdoc/require-jsdoc
52
- export type OnceListenerResult<T> = Promise<() => Promise<T>>;
53
-
54
- // TODO: Add documentation
55
- // eslint-disable-next-line jsdoc/require-jsdoc
56
- export const serverListenAndHandle = async <T>(
57
- port: number,
58
- handler: OnceListenerHandler<T>,
59
- ): OnceListenerResult<T> =>
60
- // eslint-disable-next-line promise/param-names
61
- new Promise((outerResolve, outerReject) => {
62
- const innerP = new Promise<T>((innerResolve, innerReject) => {
63
- const httpServer = createTrackedServer(port, (req, res) => {
64
- // ignore favicon
65
- if (req.url === "/favicon.ico") {
66
- res.writeHead(200, { "Content-Type": "image/x-icon" });
67
- res.end();
68
- return;
69
- }
70
- handler(req, res)
71
- .finally(() => httpServer.fullyClose())
72
- .then(
73
- (result) => innerResolve(result),
74
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
75
- (error) => innerReject(error),
76
- );
77
- });
78
- outerResolve(async () => innerP);
79
- });
80
- });
81
-
82
- // TODO: Add documentation
83
- // eslint-disable-next-line jsdoc/require-jsdoc
84
- export const endResponse = async (response: http.ServerResponse): Promise<void> =>
85
- new Promise((resolve, reject) => {
86
- try {
87
- response.end(resolve);
88
- } catch (error) {
89
- // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
90
- reject(error);
91
- }
92
- });