@workos-inc/node 7.8.0 → 7.10.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 (37) hide show
  1. package/README.md +4 -0
  2. package/lib/common/crypto/CryptoProvider.d.ts +32 -0
  3. package/lib/common/crypto/CryptoProvider.js +13 -0
  4. package/lib/common/crypto/NodeCryptoProvider.d.ts +12 -0
  5. package/lib/common/crypto/NodeCryptoProvider.js +73 -0
  6. package/lib/common/crypto/SubtleCryptoProvider.d.ts +15 -0
  7. package/lib/common/crypto/SubtleCryptoProvider.js +75 -0
  8. package/lib/common/crypto/index.d.ts +3 -0
  9. package/lib/common/crypto/index.js +19 -0
  10. package/lib/common/interfaces/http-client.interface.d.ts +20 -0
  11. package/lib/common/interfaces/http-client.interface.js +2 -0
  12. package/lib/common/interfaces/index.d.ts +1 -0
  13. package/lib/common/interfaces/index.js +1 -0
  14. package/lib/common/interfaces/workos-options.interface.d.ts +1 -0
  15. package/lib/common/net/fetch-client.d.ts +22 -0
  16. package/lib/common/net/fetch-client.js +112 -0
  17. package/lib/common/net/http-client.d.ts +39 -0
  18. package/lib/common/net/http-client.js +76 -0
  19. package/lib/common/net/index.d.ts +5 -0
  20. package/lib/common/net/index.js +31 -0
  21. package/lib/common/net/node-client.d.ts +23 -0
  22. package/lib/common/net/node-client.js +155 -0
  23. package/lib/user-management/interfaces/authenticate-with-code-options.interface.d.ts +2 -0
  24. package/lib/user-management/interfaces/authorization-url-options.interface.d.ts +2 -0
  25. package/lib/user-management/serializers/authenticate-with-code-options.serializer.js +1 -0
  26. package/lib/user-management/user-management.d.ts +1 -1
  27. package/lib/user-management/user-management.js +3 -1
  28. package/lib/user-management/user-management.spec.js +34 -0
  29. package/lib/webhooks/webhooks.d.ts +2 -2
  30. package/lib/webhooks/webhooks.js +11 -37
  31. package/lib/webhooks/webhooks.spec.js +29 -0
  32. package/lib/workos.d.ts +1 -1
  33. package/lib/workos.js +18 -12
  34. package/lib/workos.spec.js +56 -3
  35. package/package.json +2 -3
  36. package/lib/common/utils/fetch-client.d.ts +0 -31
  37. package/lib/common/utils/fetch-client.js +0 -108
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.NodeHttpClientResponse = exports.NodeHttpClient = void 0;
36
+ const http_client_1 = require("./http-client");
37
+ const http_ = __importStar(require("http"));
38
+ const https_ = __importStar(require("https"));
39
+ // `import * as http_ from 'http'` creates a "Module Namespace Exotic Object"
40
+ // which is immune to monkey-patching, whereas http_.default (in an ES Module context)
41
+ // will resolve to the same thing as require('http'), which is
42
+ // monkey-patchable. We care about this because users in their test
43
+ // suites might be using a library like "nock" which relies on the ability
44
+ // to monkey-patch and intercept calls to http.request.
45
+ const http = http_.default || http_;
46
+ const https = https_.default || https_;
47
+ class NodeHttpClient extends http_client_1.HttpClient {
48
+ constructor(baseURL, options) {
49
+ super(baseURL, options);
50
+ this.baseURL = baseURL;
51
+ this.options = options;
52
+ this.httpAgent = new http.Agent({ keepAlive: true });
53
+ this.httpsAgent = new https.Agent({ keepAlive: true });
54
+ }
55
+ getClientName() {
56
+ return 'node';
57
+ }
58
+ get(path, options) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
61
+ return yield this.nodeRequest(resourceURL, 'GET', null, options.headers);
62
+ });
63
+ }
64
+ post(path, entity, options) {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
67
+ return yield this.nodeRequest(resourceURL, 'POST', http_client_1.HttpClient.getBody(entity), Object.assign(Object.assign({}, http_client_1.HttpClient.getContentTypeHeader(entity)), options.headers));
68
+ });
69
+ }
70
+ put(path, entity, options) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
73
+ return yield this.nodeRequest(resourceURL, 'PUT', http_client_1.HttpClient.getBody(entity), Object.assign(Object.assign({}, http_client_1.HttpClient.getContentTypeHeader(entity)), options.headers));
74
+ });
75
+ }
76
+ delete(path, options) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
79
+ return yield this.nodeRequest(resourceURL, 'DELETE', null, options.headers);
80
+ });
81
+ }
82
+ nodeRequest(url, method, body, headers) {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ return new Promise((resolve, reject) => {
85
+ var _a, _b;
86
+ const isSecureConnection = url.startsWith('https');
87
+ const agent = isSecureConnection ? this.httpsAgent : this.httpAgent;
88
+ const lib = isSecureConnection ? https : http;
89
+ const { 'User-Agent': userAgent } = (_a = this.options) === null || _a === void 0 ? void 0 : _a.headers;
90
+ const options = {
91
+ method,
92
+ headers: Object.assign(Object.assign(Object.assign({ Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json' }, (_b = this.options) === null || _b === void 0 ? void 0 : _b.headers), headers), { 'User-Agent': this.addClientToUserAgent(userAgent.toString()) }),
93
+ agent,
94
+ };
95
+ const req = lib.request(url, options, (res) => __awaiter(this, void 0, void 0, function* () {
96
+ const clientResponse = new NodeHttpClientResponse(res);
97
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode > 299)) {
98
+ reject(new http_client_1.HttpClientError({
99
+ message: res.statusMessage,
100
+ response: {
101
+ status: res.statusCode,
102
+ headers: res.headers,
103
+ data: yield clientResponse.toJSON(),
104
+ },
105
+ }));
106
+ }
107
+ resolve(clientResponse);
108
+ }));
109
+ req.on('error', (err) => {
110
+ reject(new Error(err.message));
111
+ });
112
+ if (body) {
113
+ req.setHeader('Content-Length', Buffer.byteLength(body));
114
+ req.write(body);
115
+ }
116
+ req.end();
117
+ });
118
+ });
119
+ }
120
+ }
121
+ exports.NodeHttpClient = NodeHttpClient;
122
+ // tslint:disable-next-line
123
+ class NodeHttpClientResponse extends http_client_1.HttpClientResponse {
124
+ constructor(res) {
125
+ // @ts-ignore
126
+ super(res.statusCode, res.headers || {});
127
+ this._res = res;
128
+ }
129
+ getRawResponse() {
130
+ return this._res;
131
+ }
132
+ toJSON() {
133
+ return new Promise((resolve, reject) => {
134
+ const contentType = this._res.headers['content-type'];
135
+ const isJsonResponse = contentType === null || contentType === void 0 ? void 0 : contentType.includes('application/json');
136
+ if (!isJsonResponse) {
137
+ resolve(null);
138
+ }
139
+ let response = '';
140
+ this._res.setEncoding('utf8');
141
+ this._res.on('data', (chunk) => {
142
+ response += chunk;
143
+ });
144
+ this._res.once('end', () => {
145
+ try {
146
+ resolve(JSON.parse(response));
147
+ }
148
+ catch (e) {
149
+ reject(e);
150
+ }
151
+ });
152
+ });
153
+ }
154
+ }
155
+ exports.NodeHttpClientResponse = NodeHttpClientResponse;
@@ -1,5 +1,6 @@
1
1
  import { AuthenticateWithOptionsBase, SerializedAuthenticateWithOptionsBase } from './authenticate-with-options-base.interface';
2
2
  export interface AuthenticateWithCodeOptions extends AuthenticateWithOptionsBase {
3
+ codeVerifier?: string;
3
4
  code: string;
4
5
  invitationToken?: string;
5
6
  }
@@ -8,6 +9,7 @@ export interface AuthenticateUserWithCodeCredentials {
8
9
  }
9
10
  export interface SerializedAuthenticateWithCodeOptions extends SerializedAuthenticateWithOptionsBase {
10
11
  grant_type: 'authorization_code';
12
+ code_verifier?: string;
11
13
  code: string;
12
14
  invitation_token?: string;
13
15
  }
@@ -1,5 +1,7 @@
1
1
  export interface AuthorizationURLOptions {
2
2
  clientId: string;
3
+ codeChallenge?: string;
4
+ codeChallengeMethod?: 'S256';
3
5
  connectionId?: string;
4
6
  organizationId?: string;
5
7
  domainHint?: string;
@@ -6,6 +6,7 @@ const serializeAuthenticateWithCodeOptions = (options) => ({
6
6
  client_id: options.clientId,
7
7
  client_secret: options.clientSecret,
8
8
  code: options.code,
9
+ code_verifier: options.codeVerifier,
9
10
  invitation_token: options.invitationToken,
10
11
  ip_address: options.ipAddress,
11
12
  user_agent: options.userAgent,
@@ -69,7 +69,7 @@ export declare class UserManagement {
69
69
  sendInvitation(payload: SendInvitationOptions): Promise<Invitation>;
70
70
  revokeInvitation(invitationId: string): Promise<Invitation>;
71
71
  revokeSession(payload: RevokeSessionOptions): Promise<void>;
72
- getAuthorizationUrl({ connectionId, clientId, domainHint, loginHint, organizationId, provider, redirectUri, state, screenHint, }: AuthorizationURLOptions): string;
72
+ getAuthorizationUrl({ connectionId, codeChallenge, codeChallengeMethod, clientId, domainHint, loginHint, organizationId, provider, redirectUri, state, screenHint, }: AuthorizationURLOptions): string;
73
73
  getLogoutUrl({ sessionId }: {
74
74
  sessionId: string;
75
75
  }): string;
@@ -269,7 +269,7 @@ class UserManagement {
269
269
  yield this.workos.post('/user_management/sessions/revoke', (0, revoke_session_options_interface_1.serializeRevokeSessionOptions)(payload));
270
270
  });
271
271
  }
272
- getAuthorizationUrl({ connectionId, clientId, domainHint, loginHint, organizationId, provider, redirectUri, state, screenHint, }) {
272
+ getAuthorizationUrl({ connectionId, codeChallenge, codeChallengeMethod, clientId, domainHint, loginHint, organizationId, provider, redirectUri, state, screenHint, }) {
273
273
  if (!provider && !connectionId && !organizationId) {
274
274
  throw new TypeError(`Incomplete arguments. Need to specify either a 'connectionId', 'organizationId', or 'provider'.`);
275
275
  }
@@ -278,6 +278,8 @@ class UserManagement {
278
278
  }
279
279
  const query = toQueryString({
280
280
  connection_id: connectionId,
281
+ code_challenge: codeChallenge,
282
+ code_challenge_method: codeChallengeMethod,
281
283
  organization_id: organizationId,
282
284
  domain_hint: domainHint,
283
285
  login_hint: loginHint,
@@ -163,6 +163,27 @@ describe('UserManagement', () => {
163
163
  },
164
164
  });
165
165
  }));
166
+ it('sends a token authentication request when including the code_verifier', () => __awaiter(void 0, void 0, void 0, function* () {
167
+ (0, test_utils_1.fetchOnce)({ user: user_json_1.default });
168
+ const resp = yield workos.userManagement.authenticateWithCode({
169
+ clientId: 'proj_whatever',
170
+ code: 'or this',
171
+ codeVerifier: 'code_verifier_value',
172
+ });
173
+ expect((0, test_utils_1.fetchURL)()).toContain('/user_management/authenticate');
174
+ expect((0, test_utils_1.fetchBody)()).toEqual({
175
+ client_id: 'proj_whatever',
176
+ client_secret: 'sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU',
177
+ code: 'or this',
178
+ code_verifier: 'code_verifier_value',
179
+ grant_type: 'authorization_code',
180
+ });
181
+ expect(resp).toMatchObject({
182
+ user: {
183
+ email: 'test01@example.com',
184
+ },
185
+ });
186
+ }));
166
187
  it('deserializes authentication_method', () => __awaiter(void 0, void 0, void 0, function* () {
167
188
  (0, test_utils_1.fetchOnce)({
168
189
  user: user_json_1.default,
@@ -868,6 +889,19 @@ describe('UserManagement', () => {
868
889
  expect(url).toMatchSnapshot();
869
890
  });
870
891
  });
892
+ describe('with a code_challenge and code_challenge_method', () => {
893
+ it('generates an authorize url', () => {
894
+ const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
895
+ const url = workos.userManagement.getAuthorizationUrl({
896
+ provider: 'authkit',
897
+ clientId: 'proj_123',
898
+ redirectUri: 'example.com/auth/workos/callback',
899
+ codeChallenge: 'code_challenge_value',
900
+ codeChallengeMethod: 'S256',
901
+ });
902
+ expect(url).toMatchSnapshot();
903
+ });
904
+ });
871
905
  describe('with no custom api hostname', () => {
872
906
  it('generates an authorize url with the default api hostname', () => {
873
907
  const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
@@ -1,6 +1,7 @@
1
1
  import { Event } from '../common/interfaces';
2
2
  export declare class Webhooks {
3
- private encoder;
3
+ private cryptoProvider;
4
+ constructor(subtleCrypto?: typeof crypto.subtle);
4
5
  constructEvent({ payload, sigHeader, secret, tolerance, }: {
5
6
  payload: unknown;
6
7
  sigHeader: string;
@@ -15,5 +16,4 @@ export declare class Webhooks {
15
16
  }): Promise<boolean>;
16
17
  getTimestampAndSignatureHash(sigHeader: string): [string, string];
17
18
  computeSignature(timestamp: any, payload: any, secret: string): Promise<string>;
18
- secureCompare(stringA: string, stringB: string): Promise<boolean>;
19
19
  }
@@ -12,9 +12,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.Webhooks = void 0;
13
13
  const exceptions_1 = require("../common/exceptions");
14
14
  const serializers_1 = require("../common/serializers");
15
+ const crypto_1 = require("../common/crypto");
15
16
  class Webhooks {
16
- constructor() {
17
- this.encoder = new TextEncoder();
17
+ constructor(subtleCrypto) {
18
+ if (typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined') {
19
+ this.cryptoProvider = new crypto_1.SubtleCryptoProvider(subtleCrypto);
20
+ }
21
+ else {
22
+ this.cryptoProvider = new crypto_1.NodeCryptoProvider();
23
+ }
18
24
  }
19
25
  constructEvent({ payload, sigHeader, secret, tolerance = 180000, }) {
20
26
  return __awaiter(this, void 0, void 0, function* () {
@@ -34,7 +40,8 @@ class Webhooks {
34
40
  throw new exceptions_1.SignatureVerificationException('Timestamp outside the tolerance zone');
35
41
  }
36
42
  const expectedSig = yield this.computeSignature(timestamp, payload, secret);
37
- if ((yield this.secureCompare(expectedSig, signatureHash)) === false) {
43
+ if ((yield this.cryptoProvider.secureCompare(expectedSig, signatureHash)) ===
44
+ false) {
38
45
  throw new exceptions_1.SignatureVerificationException('Signature hash does not match the expected signature hash for payload');
39
46
  }
40
47
  return true;
@@ -54,41 +61,8 @@ class Webhooks {
54
61
  return __awaiter(this, void 0, void 0, function* () {
55
62
  payload = JSON.stringify(payload);
56
63
  const signedPayload = `${timestamp}.${payload}`;
57
- const key = yield crypto.subtle.importKey('raw', this.encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
58
- const signatureBuffer = yield crypto.subtle.sign('HMAC', key, this.encoder.encode(signedPayload));
59
- // crypto.subtle returns the signature in base64 format. This must be
60
- // encoded in hex to match the CryptoProvider contract. We map each byte in
61
- // the buffer to its corresponding hex octet and then combine into a string.
62
- const signatureBytes = new Uint8Array(signatureBuffer);
63
- const signatureHexCodes = new Array(signatureBytes.length);
64
- for (let i = 0; i < signatureBytes.length; i++) {
65
- signatureHexCodes[i] = byteHexMapping[signatureBytes[i]];
66
- }
67
- return signatureHexCodes.join('');
68
- });
69
- }
70
- secureCompare(stringA, stringB) {
71
- return __awaiter(this, void 0, void 0, function* () {
72
- const bufferA = this.encoder.encode(stringA);
73
- const bufferB = this.encoder.encode(stringB);
74
- if (bufferA.length !== bufferB.length) {
75
- return false;
76
- }
77
- const algorithm = { name: 'HMAC', hash: 'SHA-256' };
78
- const key = (yield crypto.subtle.generateKey(algorithm, false, [
79
- 'sign',
80
- 'verify',
81
- ]));
82
- const hmac = yield crypto.subtle.sign(algorithm, key, bufferA);
83
- const equal = yield crypto.subtle.verify(algorithm, key, hmac, bufferB);
84
- return equal;
64
+ return yield this.cryptoProvider.computeHMACSignatureAsync(signedPayload, secret);
85
65
  });
86
66
  }
87
67
  }
88
68
  exports.Webhooks = Webhooks;
89
- // Cached mapping of byte to hex representation. We do this once to avoid re-
90
- // computing every time we need to convert the result of a signature to hex.
91
- const byteHexMapping = new Array(256);
92
- for (let i = 0; i < byteHexMapping.length; i++) {
93
- byteHexMapping[i] = i.toString(16).padStart(2, '0');
94
- }
@@ -17,6 +17,7 @@ const workos_1 = require("../workos");
17
17
  const webhook_json_1 = __importDefault(require("./fixtures/webhook.json"));
18
18
  const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
19
19
  const exceptions_1 = require("../common/exceptions");
20
+ const crypto_2 = require("../common/crypto");
20
21
  describe('Webhooks', () => {
21
22
  let payload;
22
23
  let secret;
@@ -187,4 +188,32 @@ describe('Webhooks', () => {
187
188
  expect(signature).toEqual(signatureHash);
188
189
  }));
189
190
  });
191
+ describe('when in an environment that supports SubtleCrypto', () => {
192
+ it('automatically uses the subtle crypto library', () => {
193
+ // tslint:disable-next-line
194
+ expect(workos.webhooks['cryptoProvider']).toBeInstanceOf(crypto_2.SubtleCryptoProvider);
195
+ });
196
+ });
197
+ describe('CryptoProvider', () => {
198
+ describe('when computing HMAC signature', () => {
199
+ it('returns the same for the Node crypto and Web Crypto versions', () => __awaiter(void 0, void 0, void 0, function* () {
200
+ const nodeCryptoProvider = new crypto_2.NodeCryptoProvider();
201
+ const subtleCryptoProvider = new crypto_2.SubtleCryptoProvider();
202
+ const stringifiedPayload = JSON.stringify(payload);
203
+ const payloadHMAC = `${timestamp}.${stringifiedPayload}`;
204
+ const nodeCompare = yield nodeCryptoProvider.computeHMACSignatureAsync(payloadHMAC, secret);
205
+ const subtleCompare = yield subtleCryptoProvider.computeHMACSignatureAsync(payloadHMAC, secret);
206
+ expect(nodeCompare).toEqual(subtleCompare);
207
+ }));
208
+ });
209
+ describe('when securely comparing', () => {
210
+ it('returns the same for the Node crypto and Web Crypto versions', () => __awaiter(void 0, void 0, void 0, function* () {
211
+ const nodeCryptoProvider = new crypto_2.NodeCryptoProvider();
212
+ const subtleCryptoProvider = new crypto_2.SubtleCryptoProvider();
213
+ const signature = yield workos.webhooks.computeSignature(timestamp, payload, secret);
214
+ expect(nodeCryptoProvider.secureCompare(signature, signatureHash)).toEqual(subtleCryptoProvider.secureCompare(signature, signatureHash));
215
+ expect(nodeCryptoProvider.secureCompare(signature, 'foo')).toEqual(subtleCryptoProvider.secureCompare(signature, 'foo'));
216
+ }));
217
+ });
218
+ });
190
219
  });
package/lib/workos.d.ts CHANGED
@@ -39,5 +39,5 @@ export declare class WorkOS {
39
39
  }>;
40
40
  delete(path: string, query?: any): Promise<void>;
41
41
  emitWarning(warning: string): void;
42
- private handleFetchError;
42
+ private handleHttpError;
43
43
  }
package/lib/workos.js CHANGED
@@ -23,8 +23,8 @@ const mfa_1 = require("./mfa/mfa");
23
23
  const audit_logs_1 = require("./audit-logs/audit-logs");
24
24
  const user_management_1 = require("./user-management/user-management");
25
25
  const bad_request_exception_1 = require("./common/exceptions/bad-request.exception");
26
- const fetch_client_1 = require("./common/utils/fetch-client");
27
- const VERSION = '7.8.0';
26
+ const net_1 = require("./common/net");
27
+ const VERSION = '7.10.0';
28
28
  const DEFAULT_HOSTNAME = 'api.workos.com';
29
29
  class WorkOS {
30
30
  constructor(key, options = {}) {
@@ -64,7 +64,7 @@ class WorkOS {
64
64
  const { name, version } = options.appInfo;
65
65
  userAgent += ` ${name}: ${version}`;
66
66
  }
67
- this.client = new fetch_client_1.FetchClient(this.baseURL, Object.assign(Object.assign({}, options.config), { headers: Object.assign(Object.assign({}, (_a = options.config) === null || _a === void 0 ? void 0 : _a.headers), { Authorization: `Bearer ${this.key}`, 'User-Agent': userAgent }) }));
67
+ this.client = (0, net_1.createHttpClient)(this.baseURL, Object.assign(Object.assign({}, options.config), { headers: Object.assign(Object.assign({}, (_a = options.config) === null || _a === void 0 ? void 0 : _a.headers), { Authorization: `Bearer ${this.key}`, 'User-Agent': userAgent }) }), options.fetchFn);
68
68
  }
69
69
  get version() {
70
70
  return VERSION;
@@ -76,13 +76,14 @@ class WorkOS {
76
76
  requestHeaders['Idempotency-Key'] = options.idempotencyKey;
77
77
  }
78
78
  try {
79
- return yield this.client.post(path, entity, {
79
+ const res = yield this.client.post(path, entity, {
80
80
  params: options.query,
81
81
  headers: requestHeaders,
82
82
  });
83
+ return { data: yield res.toJSON() };
83
84
  }
84
85
  catch (error) {
85
- this.handleFetchError({ path, error });
86
+ this.handleHttpError({ path, error });
86
87
  throw error;
87
88
  }
88
89
  });
@@ -91,15 +92,16 @@ class WorkOS {
91
92
  return __awaiter(this, void 0, void 0, function* () {
92
93
  try {
93
94
  const { accessToken } = options;
94
- return yield this.client.get(path, {
95
+ const res = yield this.client.get(path, {
95
96
  params: options.query,
96
97
  headers: accessToken
97
98
  ? { Authorization: `Bearer ${accessToken}` }
98
99
  : undefined,
99
100
  });
101
+ return { data: yield res.toJSON() };
100
102
  }
101
103
  catch (error) {
102
- this.handleFetchError({ path, error });
104
+ this.handleHttpError({ path, error });
103
105
  throw error;
104
106
  }
105
107
  });
@@ -111,13 +113,14 @@ class WorkOS {
111
113
  requestHeaders['Idempotency-Key'] = options.idempotencyKey;
112
114
  }
113
115
  try {
114
- return yield this.client.put(path, entity, {
116
+ const res = yield this.client.put(path, entity, {
115
117
  params: options.query,
116
118
  headers: requestHeaders,
117
119
  });
120
+ return { data: yield res.toJSON() };
118
121
  }
119
122
  catch (error) {
120
- this.handleFetchError({ path, error });
123
+ this.handleHttpError({ path, error });
121
124
  throw error;
122
125
  }
123
126
  });
@@ -130,7 +133,7 @@ class WorkOS {
130
133
  });
131
134
  }
132
135
  catch (error) {
133
- this.handleFetchError({ path, error });
136
+ this.handleHttpError({ path, error });
134
137
  throw error;
135
138
  }
136
139
  });
@@ -143,12 +146,15 @@ class WorkOS {
143
146
  }
144
147
  return process.emitWarning(warning, 'WorkOS');
145
148
  }
146
- handleFetchError({ path, error }) {
149
+ handleHttpError({ path, error }) {
147
150
  var _a;
151
+ if (!(error instanceof net_1.HttpClientError)) {
152
+ throw new Error(`Unexpected error: ${error}`);
153
+ }
148
154
  const { response } = error;
149
155
  if (response) {
150
156
  const { status, data, headers } = response;
151
- const requestID = (_a = headers.get('X-Request-ID')) !== null && _a !== void 0 ? _a : '';
157
+ const requestID = (_a = headers['X-Request-ID']) !== null && _a !== void 0 ? _a : '';
152
158
  const { code, error_description: errorDescription, error, errors, message, } = data;
153
159
  switch (status) {
154
160
  case 401: {
@@ -18,6 +18,7 @@ const promises_1 = __importDefault(require("fs/promises"));
18
18
  const exceptions_1 = require("./common/exceptions");
19
19
  const workos_1 = require("./workos");
20
20
  const rate_limit_exceeded_exception_1 = require("./common/exceptions/rate-limit-exceeded.exception");
21
+ const net_1 = require("./common/net");
21
22
  describe('WorkOS', () => {
22
23
  beforeEach(() => jest_fetch_mock_1.default.resetMocks());
23
24
  describe('constructor', () => {
@@ -95,10 +96,40 @@ describe('WorkOS', () => {
95
96
  });
96
97
  yield workos.post('/somewhere', {});
97
98
  expect((0, test_utils_1.fetchHeaders)()).toMatchObject({
98
- 'User-Agent': `workos-node/${packageJson.version} fooApp: 1.0.0`,
99
+ 'User-Agent': `workos-node/${packageJson.version}/fetch fooApp: 1.0.0`,
99
100
  });
100
101
  }));
101
102
  });
103
+ describe('when no `appInfo` option is provided', () => {
104
+ it('adds the HTTP client name to the user-agent', () => __awaiter(void 0, void 0, void 0, function* () {
105
+ (0, test_utils_1.fetchOnce)('{}');
106
+ const packageJson = JSON.parse(yield promises_1.default.readFile('package.json', 'utf8'));
107
+ const workos = new workos_1.WorkOS('sk_test');
108
+ yield workos.post('/somewhere', {});
109
+ expect((0, test_utils_1.fetchHeaders)()).toMatchObject({
110
+ 'User-Agent': `workos-node/${packageJson.version}/fetch`,
111
+ });
112
+ }));
113
+ });
114
+ describe('when no `appInfo` option is provided', () => {
115
+ it('adds the HTTP client name to the user-agent', () => __awaiter(void 0, void 0, void 0, function* () {
116
+ (0, test_utils_1.fetchOnce)('{}');
117
+ const packageJson = JSON.parse(yield promises_1.default.readFile('package.json', 'utf8'));
118
+ const workos = new workos_1.WorkOS('sk_test');
119
+ yield workos.post('/somewhere', {});
120
+ expect((0, test_utils_1.fetchHeaders)()).toMatchObject({
121
+ 'User-Agent': `workos-node/${packageJson.version}/fetch`,
122
+ });
123
+ }));
124
+ });
125
+ describe('when using an environment that supports fetch', () => {
126
+ it('automatically uses the fetch HTTP client', () => {
127
+ const workos = new workos_1.WorkOS('sk_test');
128
+ // Bracket notation gets past private visibility
129
+ // tslint:disable-next-line
130
+ expect(workos['client']).toBeInstanceOf(net_1.FetchHttpClient);
131
+ });
132
+ });
102
133
  });
103
134
  describe('version', () => {
104
135
  it('matches the version in `package.json`', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -177,12 +208,34 @@ describe('WorkOS', () => {
177
208
  }));
178
209
  });
179
210
  describe('when the entity is null', () => {
180
- it('sends a null body', () => __awaiter(void 0, void 0, void 0, function* () {
211
+ it('sends an empty string body', () => __awaiter(void 0, void 0, void 0, function* () {
181
212
  (0, test_utils_1.fetchOnce)();
182
213
  const workos = new workos_1.WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
183
214
  yield workos.post('/somewhere', null);
184
- expect((0, test_utils_1.fetchBody)({ raw: true })).toBeNull();
215
+ expect((0, test_utils_1.fetchBody)({ raw: true })).toBe('');
185
216
  }));
186
217
  });
187
218
  });
219
+ describe('when in an environment that does not support fetch', () => {
220
+ const fetchFn = globalThis.fetch;
221
+ beforeEach(() => {
222
+ // @ts-ignore
223
+ delete globalThis.fetch;
224
+ });
225
+ afterEach(() => {
226
+ globalThis.fetch = fetchFn;
227
+ });
228
+ it('automatically uses the node HTTP client', () => {
229
+ const workos = new workos_1.WorkOS('sk_test_key');
230
+ // tslint:disable-next-line
231
+ expect(workos['client']).toBeInstanceOf(net_1.NodeHttpClient);
232
+ });
233
+ it('uses a fetch function if provided', () => {
234
+ const workos = new workos_1.WorkOS('sk_test_key', {
235
+ fetchFn,
236
+ });
237
+ // tslint:disable-next-line
238
+ expect(workos['client']).toBeInstanceOf(net_1.FetchHttpClient);
239
+ });
240
+ });
188
241
  });
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "7.8.0",
2
+ "version": "7.10.0",
3
3
  "name": "@workos-inc/node",
4
4
  "author": "WorkOS",
5
5
  "description": "A Node wrapper for the WorkOS API",
@@ -13,9 +13,8 @@
13
13
  "yarn": "1.22.19"
14
14
  },
15
15
  "engines": {
16
- "node": ">=19"
16
+ "node": ">=16"
17
17
  },
18
- "engineStrict": true,
19
18
  "main": "lib/index.js",
20
19
  "typings": "lib/index.d.ts",
21
20
  "files": [
@@ -1,31 +0,0 @@
1
- export declare class FetchClient {
2
- readonly baseURL: string;
3
- readonly options?: RequestInit | undefined;
4
- constructor(baseURL: string, options?: RequestInit | undefined);
5
- get(path: string, options: {
6
- params?: Record<string, any>;
7
- headers?: HeadersInit;
8
- }): Promise<{
9
- data: any;
10
- }>;
11
- post<Entity = any>(path: string, entity: Entity, options: {
12
- params?: Record<string, any>;
13
- headers?: HeadersInit;
14
- }): Promise<{
15
- data: any;
16
- }>;
17
- put<Entity = any>(path: string, entity: Entity, options: {
18
- params?: Record<string, any>;
19
- headers?: HeadersInit;
20
- }): Promise<{
21
- data: any;
22
- }>;
23
- delete(path: string, options: {
24
- params?: Record<string, any>;
25
- headers?: HeadersInit;
26
- }): Promise<{
27
- data: any;
28
- }>;
29
- private getResourceURL;
30
- private fetch;
31
- }