@workos-inc/node 7.9.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.
- package/README.md +4 -0
- package/lib/common/crypto/CryptoProvider.d.ts +32 -0
- package/lib/common/crypto/CryptoProvider.js +13 -0
- package/lib/common/crypto/NodeCryptoProvider.d.ts +12 -0
- package/lib/common/crypto/NodeCryptoProvider.js +73 -0
- package/lib/common/crypto/SubtleCryptoProvider.d.ts +15 -0
- package/lib/common/crypto/SubtleCryptoProvider.js +75 -0
- package/lib/common/crypto/index.d.ts +3 -0
- package/lib/common/crypto/index.js +19 -0
- package/lib/common/interfaces/http-client.interface.d.ts +20 -0
- package/lib/common/interfaces/http-client.interface.js +2 -0
- package/lib/common/interfaces/index.d.ts +1 -0
- package/lib/common/interfaces/index.js +1 -0
- package/lib/common/interfaces/workos-options.interface.d.ts +1 -0
- package/lib/common/net/fetch-client.d.ts +22 -0
- package/lib/common/net/fetch-client.js +112 -0
- package/lib/common/net/http-client.d.ts +39 -0
- package/lib/common/net/http-client.js +76 -0
- package/lib/common/net/index.d.ts +5 -0
- package/lib/common/net/index.js +31 -0
- package/lib/common/net/node-client.d.ts +23 -0
- package/lib/common/net/node-client.js +155 -0
- package/lib/webhooks/webhooks.d.ts +2 -2
- package/lib/webhooks/webhooks.js +11 -37
- package/lib/webhooks/webhooks.spec.js +29 -0
- package/lib/workos.d.ts +1 -1
- package/lib/workos.js +18 -12
- package/lib/workos.spec.js +56 -3
- package/package.json +2 -3
- package/lib/common/utils/fetch-client.d.ts +0 -31
- package/lib/common/utils/fetch-client.js +0 -108
package/README.md
CHANGED
|
@@ -9,6 +9,10 @@ The WorkOS library for Node.js provides convenient access to the WorkOS API from
|
|
|
9
9
|
|
|
10
10
|
See the [API Reference](https://workos.com/docs/reference/client-libraries) for Node.js usage examples.
|
|
11
11
|
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
Node 16 or higher.
|
|
15
|
+
|
|
12
16
|
## Installation
|
|
13
17
|
|
|
14
18
|
Install the package with:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface encapsulating the various crypto computations used by the library,
|
|
3
|
+
* allowing pluggable underlying crypto implementations.
|
|
4
|
+
*/
|
|
5
|
+
export declare abstract class CryptoProvider {
|
|
6
|
+
encoder: TextEncoder;
|
|
7
|
+
/**
|
|
8
|
+
* Computes a SHA-256 HMAC given a secret and a payload (encoded in UTF-8).
|
|
9
|
+
* The output HMAC should be encoded in hexadecimal.
|
|
10
|
+
*
|
|
11
|
+
* Sample values for implementations:
|
|
12
|
+
* - computeHMACSignature('', 'test_secret') => 'f7f9bd47fb987337b5796fdc1fdb9ba221d0d5396814bfcaf9521f43fd8927fd'
|
|
13
|
+
* - computeHMACSignature('\ud83d\ude00', 'test_secret') => '837da296d05c4fe31f61d5d7ead035099d9585a5bcde87de952012a78f0b0c43
|
|
14
|
+
*/
|
|
15
|
+
abstract computeHMACSignature(payload: string, secret: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Asynchronous version of `computeHMACSignature`. Some implementations may
|
|
18
|
+
* only allow support async signature computation.
|
|
19
|
+
*
|
|
20
|
+
* Computes a SHA-256 HMAC given a secret and a payload (encoded in UTF-8).
|
|
21
|
+
* The output HMAC should be encoded in hexadecimal.
|
|
22
|
+
*
|
|
23
|
+
* Sample values for implementations:
|
|
24
|
+
* - computeHMACSignature('', 'test_secret') => 'f7f9bd47fb987337b5796fdc1fdb9ba221d0d5396814bfcaf9521f43fd8927fd'
|
|
25
|
+
* - computeHMACSignature('\ud83d\ude00', 'test_secret') => '837da296d05c4fe31f61d5d7ead035099d9585a5bcde87de952012a78f0b0c43
|
|
26
|
+
*/
|
|
27
|
+
abstract computeHMACSignatureAsync(payload: string, secret: string): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Cryptographically determine whether two signatures are equal
|
|
30
|
+
*/
|
|
31
|
+
abstract secureCompare(stringA: string, stringB: string): Promise<boolean>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CryptoProvider = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Interface encapsulating the various crypto computations used by the library,
|
|
6
|
+
* allowing pluggable underlying crypto implementations.
|
|
7
|
+
*/
|
|
8
|
+
class CryptoProvider {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.encoder = new TextEncoder();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.CryptoProvider = CryptoProvider;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CryptoProvider } from './CryptoProvider';
|
|
2
|
+
/**
|
|
3
|
+
* `CryptoProvider which uses the Node `crypto` package for its computations.
|
|
4
|
+
*/
|
|
5
|
+
export declare class NodeCryptoProvider extends CryptoProvider {
|
|
6
|
+
/** @override */
|
|
7
|
+
computeHMACSignature(payload: string, secret: string): string;
|
|
8
|
+
/** @override */
|
|
9
|
+
computeHMACSignatureAsync(payload: string, secret: string): Promise<string>;
|
|
10
|
+
/** @override */
|
|
11
|
+
secureCompare(stringA: string, stringB: string): Promise<boolean>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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.NodeCryptoProvider = void 0;
|
|
36
|
+
const crypto = __importStar(require("crypto"));
|
|
37
|
+
const CryptoProvider_1 = require("./CryptoProvider");
|
|
38
|
+
/**
|
|
39
|
+
* `CryptoProvider which uses the Node `crypto` package for its computations.
|
|
40
|
+
*/
|
|
41
|
+
class NodeCryptoProvider extends CryptoProvider_1.CryptoProvider {
|
|
42
|
+
/** @override */
|
|
43
|
+
computeHMACSignature(payload, secret) {
|
|
44
|
+
return crypto
|
|
45
|
+
.createHmac('sha256', secret)
|
|
46
|
+
.update(payload, 'utf8')
|
|
47
|
+
.digest('hex');
|
|
48
|
+
}
|
|
49
|
+
/** @override */
|
|
50
|
+
computeHMACSignatureAsync(payload, secret) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const signature = yield this.computeHMACSignature(payload, secret);
|
|
53
|
+
return signature;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** @override */
|
|
57
|
+
secureCompare(stringA, stringB) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
const bufferA = this.encoder.encode(stringA);
|
|
60
|
+
const bufferB = this.encoder.encode(stringB);
|
|
61
|
+
if (bufferA.length !== bufferB.length) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
// Generate a random key for HMAC
|
|
65
|
+
const key = crypto.randomBytes(32); // Generates a 256-bit key
|
|
66
|
+
const hmacA = crypto.createHmac('sha256', key).update(bufferA).digest();
|
|
67
|
+
const hmacB = crypto.createHmac('sha256', key).update(bufferB).digest();
|
|
68
|
+
// Perform a constant time comparison
|
|
69
|
+
return crypto.timingSafeEqual(hmacA, hmacB);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.NodeCryptoProvider = NodeCryptoProvider;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CryptoProvider } from './CryptoProvider';
|
|
2
|
+
/**
|
|
3
|
+
* `CryptoProvider which uses the SubtleCrypto interface of the Web Crypto API.
|
|
4
|
+
*
|
|
5
|
+
* This only supports asynchronous operations.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SubtleCryptoProvider extends CryptoProvider {
|
|
8
|
+
subtleCrypto: SubtleCrypto;
|
|
9
|
+
constructor(subtleCrypto?: SubtleCrypto);
|
|
10
|
+
computeHMACSignature(_payload: string, _secret: string): string;
|
|
11
|
+
/** @override */
|
|
12
|
+
computeHMACSignatureAsync(payload: string, secret: string): Promise<string>;
|
|
13
|
+
/** @override */
|
|
14
|
+
secureCompare(stringA: string, stringB: string): Promise<boolean>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SubtleCryptoProvider = void 0;
|
|
13
|
+
const CryptoProvider_1 = require("./CryptoProvider");
|
|
14
|
+
/**
|
|
15
|
+
* `CryptoProvider which uses the SubtleCrypto interface of the Web Crypto API.
|
|
16
|
+
*
|
|
17
|
+
* This only supports asynchronous operations.
|
|
18
|
+
*/
|
|
19
|
+
class SubtleCryptoProvider extends CryptoProvider_1.CryptoProvider {
|
|
20
|
+
constructor(subtleCrypto) {
|
|
21
|
+
super();
|
|
22
|
+
// If no subtle crypto is interface, default to the global namespace. This
|
|
23
|
+
// is to allow custom interfaces (eg. using the Node webcrypto interface in
|
|
24
|
+
// tests).
|
|
25
|
+
this.subtleCrypto = subtleCrypto || crypto.subtle;
|
|
26
|
+
}
|
|
27
|
+
computeHMACSignature(_payload, _secret) {
|
|
28
|
+
throw new Error('SubleCryptoProvider cannot be used in a synchronous context.');
|
|
29
|
+
}
|
|
30
|
+
/** @override */
|
|
31
|
+
computeHMACSignatureAsync(payload, secret) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const encoder = new TextEncoder();
|
|
34
|
+
const key = yield this.subtleCrypto.importKey('raw', encoder.encode(secret), {
|
|
35
|
+
name: 'HMAC',
|
|
36
|
+
hash: { name: 'SHA-256' },
|
|
37
|
+
}, false, ['sign']);
|
|
38
|
+
const signatureBuffer = yield this.subtleCrypto.sign('hmac', key, encoder.encode(payload));
|
|
39
|
+
// crypto.subtle returns the signature in base64 format. This must be
|
|
40
|
+
// encoded in hex to match the CryptoProvider contract. We map each byte in
|
|
41
|
+
// the buffer to its corresponding hex octet and then combine into a string.
|
|
42
|
+
const signatureBytes = new Uint8Array(signatureBuffer);
|
|
43
|
+
const signatureHexCodes = new Array(signatureBytes.length);
|
|
44
|
+
for (let i = 0; i < signatureBytes.length; i++) {
|
|
45
|
+
signatureHexCodes[i] = byteHexMapping[signatureBytes[i]];
|
|
46
|
+
}
|
|
47
|
+
return signatureHexCodes.join('');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** @override */
|
|
51
|
+
secureCompare(stringA, stringB) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
const bufferA = this.encoder.encode(stringA);
|
|
54
|
+
const bufferB = this.encoder.encode(stringB);
|
|
55
|
+
if (bufferA.length !== bufferB.length) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const algorithm = { name: 'HMAC', hash: 'SHA-256' };
|
|
59
|
+
const key = (yield crypto.subtle.generateKey(algorithm, false, [
|
|
60
|
+
'sign',
|
|
61
|
+
'verify',
|
|
62
|
+
]));
|
|
63
|
+
const hmac = yield crypto.subtle.sign(algorithm, key, bufferA);
|
|
64
|
+
const equal = yield crypto.subtle.verify(algorithm, key, hmac, bufferB);
|
|
65
|
+
return equal;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.SubtleCryptoProvider = SubtleCryptoProvider;
|
|
70
|
+
// Cached mapping of byte to hex representation. We do this once to avoid re-
|
|
71
|
+
// computing every time we need to convert the result of a signature to hex.
|
|
72
|
+
const byteHexMapping = new Array(256);
|
|
73
|
+
for (let i = 0; i < byteHexMapping.length; i++) {
|
|
74
|
+
byteHexMapping[i] = i.toString(16).padStart(2, '0');
|
|
75
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./NodeCryptoProvider"), exports);
|
|
18
|
+
__exportStar(require("./SubtleCryptoProvider"), exports);
|
|
19
|
+
__exportStar(require("./CryptoProvider"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type RequestHeaders = Record<string, string | number | string[]>;
|
|
2
|
+
export type RequestOptions = {
|
|
3
|
+
params?: Record<string, any>;
|
|
4
|
+
headers?: RequestHeaders;
|
|
5
|
+
};
|
|
6
|
+
export type ResponseHeaderValue = string | string[];
|
|
7
|
+
export type ResponseHeaders = Record<string, ResponseHeaderValue>;
|
|
8
|
+
export interface HttpClientInterface {
|
|
9
|
+
getClientName: () => string;
|
|
10
|
+
get(path: string, options: RequestOptions): any;
|
|
11
|
+
post<Entity = any>(path: string, entity: Entity, options: RequestOptions): any;
|
|
12
|
+
put<Entity = any>(path: string, entity: Entity, options: RequestOptions): any;
|
|
13
|
+
delete(path: string, options: RequestOptions): any;
|
|
14
|
+
}
|
|
15
|
+
export interface HttpClientResponseInterface {
|
|
16
|
+
getStatusCode: () => number;
|
|
17
|
+
getHeaders: () => ResponseHeaders;
|
|
18
|
+
getRawResponse: () => unknown;
|
|
19
|
+
toJSON: () => Promise<any> | null;
|
|
20
|
+
}
|
|
@@ -23,3 +23,4 @@ __exportStar(require("./unprocessable-entity-error.interface"), exports);
|
|
|
23
23
|
__exportStar(require("./workos-options.interface"), exports);
|
|
24
24
|
__exportStar(require("./workos-response-error.interface"), exports);
|
|
25
25
|
__exportStar(require("./pagination-options.interface"), exports);
|
|
26
|
+
__exportStar(require("./http-client.interface"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HttpClientInterface, HttpClientResponseInterface, RequestOptions, ResponseHeaders } from '../interfaces/http-client.interface';
|
|
2
|
+
import { HttpClient, HttpClientResponse } from './http-client';
|
|
3
|
+
export declare class FetchHttpClient extends HttpClient implements HttpClientInterface {
|
|
4
|
+
readonly baseURL: string;
|
|
5
|
+
readonly options?: RequestInit | undefined;
|
|
6
|
+
private readonly _fetchFn;
|
|
7
|
+
constructor(baseURL: string, options?: RequestInit | undefined, fetchFn?: typeof fetch);
|
|
8
|
+
/** @override */
|
|
9
|
+
getClientName(): string;
|
|
10
|
+
get(path: string, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
11
|
+
post<Entity = any>(path: string, entity: Entity, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
12
|
+
put<Entity = any>(path: string, entity: Entity, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
13
|
+
delete(path: string, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
14
|
+
private fetchRequest;
|
|
15
|
+
}
|
|
16
|
+
export declare class FetchHttpClientResponse extends HttpClientResponse implements HttpClientResponseInterface {
|
|
17
|
+
_res: Response;
|
|
18
|
+
constructor(res: Response);
|
|
19
|
+
getRawResponse(): Response;
|
|
20
|
+
toJSON(): Promise<any> | null;
|
|
21
|
+
static _transformHeadersToObject(headers: Headers): ResponseHeaders;
|
|
22
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.FetchHttpClientResponse = exports.FetchHttpClient = void 0;
|
|
13
|
+
const http_client_1 = require("./http-client");
|
|
14
|
+
class FetchHttpClient extends http_client_1.HttpClient {
|
|
15
|
+
constructor(baseURL, options, fetchFn) {
|
|
16
|
+
super(baseURL, options);
|
|
17
|
+
this.baseURL = baseURL;
|
|
18
|
+
this.options = options;
|
|
19
|
+
// Default to global fetch if available
|
|
20
|
+
if (!fetchFn) {
|
|
21
|
+
if (!globalThis.fetch) {
|
|
22
|
+
throw new Error('Fetch function not defined in the global scope and no replacement was provided.');
|
|
23
|
+
}
|
|
24
|
+
fetchFn = globalThis.fetch;
|
|
25
|
+
}
|
|
26
|
+
this._fetchFn = fetchFn;
|
|
27
|
+
}
|
|
28
|
+
/** @override */
|
|
29
|
+
getClientName() {
|
|
30
|
+
return 'fetch';
|
|
31
|
+
}
|
|
32
|
+
get(path, options) {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
|
|
35
|
+
return yield this.fetchRequest(resourceURL, 'GET', null, options.headers);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
post(path, entity, options) {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
|
|
41
|
+
return yield this.fetchRequest(resourceURL, 'POST', http_client_1.HttpClient.getBody(entity), Object.assign(Object.assign({}, http_client_1.HttpClient.getContentTypeHeader(entity)), options.headers));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
put(path, entity, options) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
|
|
47
|
+
return yield this.fetchRequest(resourceURL, 'PUT', http_client_1.HttpClient.getBody(entity), Object.assign(Object.assign({}, http_client_1.HttpClient.getContentTypeHeader(entity)), options.headers));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
delete(path, options) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const resourceURL = http_client_1.HttpClient.getResourceURL(this.baseURL, path, options.params);
|
|
53
|
+
return yield this.fetchRequest(resourceURL, 'DELETE', null, options.headers);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
fetchRequest(url, method, body, headers) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
// For methods which expect payloads, we should always pass a body value
|
|
60
|
+
// even when it is empty. Without this, some JS runtimes (eg. Deno) will
|
|
61
|
+
// inject a second Content-Length header.
|
|
62
|
+
const methodHasPayload = method === 'POST' || method === 'PUT' || method === 'PATCH';
|
|
63
|
+
const requestBody = body || (methodHasPayload ? '' : undefined);
|
|
64
|
+
const { 'User-Agent': userAgent } = (_a = this.options) === null || _a === void 0 ? void 0 : _a.headers;
|
|
65
|
+
const res = yield this._fetchFn(url, {
|
|
66
|
+
method,
|
|
67
|
+
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()) }),
|
|
68
|
+
body: requestBody,
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
throw new http_client_1.HttpClientError({
|
|
72
|
+
message: res.statusText,
|
|
73
|
+
response: {
|
|
74
|
+
status: res.status,
|
|
75
|
+
headers: res.headers,
|
|
76
|
+
data: yield res.json(),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return new FetchHttpClientResponse(res);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.FetchHttpClient = FetchHttpClient;
|
|
85
|
+
// tslint:disable-next-line
|
|
86
|
+
class FetchHttpClientResponse extends http_client_1.HttpClientResponse {
|
|
87
|
+
constructor(res) {
|
|
88
|
+
super(res.status, FetchHttpClientResponse._transformHeadersToObject(res.headers));
|
|
89
|
+
this._res = res;
|
|
90
|
+
}
|
|
91
|
+
getRawResponse() {
|
|
92
|
+
return this._res;
|
|
93
|
+
}
|
|
94
|
+
toJSON() {
|
|
95
|
+
const contentType = this._res.headers.get('content-type');
|
|
96
|
+
const isJsonResponse = contentType === null || contentType === void 0 ? void 0 : contentType.includes('application/json');
|
|
97
|
+
return isJsonResponse ? this._res.json() : null;
|
|
98
|
+
}
|
|
99
|
+
static _transformHeadersToObject(headers) {
|
|
100
|
+
// Fetch uses a Headers instance so this must be converted to a barebones
|
|
101
|
+
// JS object to meet the HttpClient interface.
|
|
102
|
+
const headersObj = {};
|
|
103
|
+
for (const entry of Object.entries(headers)) {
|
|
104
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
105
|
+
throw new Error('Response objects produced by the fetch function given to FetchHttpClient do not have an iterable headers map. Response#headers should be an iterable object.');
|
|
106
|
+
}
|
|
107
|
+
headersObj[entry[0]] = entry[1];
|
|
108
|
+
}
|
|
109
|
+
return headersObj;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.FetchHttpClientResponse = FetchHttpClientResponse;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { HttpClientInterface, HttpClientResponseInterface, RequestHeaders, RequestOptions, ResponseHeaders } from '../interfaces/http-client.interface';
|
|
2
|
+
export declare abstract class HttpClient implements HttpClientInterface {
|
|
3
|
+
readonly baseURL: string;
|
|
4
|
+
readonly options?: RequestInit | undefined;
|
|
5
|
+
constructor(baseURL: string, options?: RequestInit | undefined);
|
|
6
|
+
/** The HTTP client name used for diagnotics */
|
|
7
|
+
getClientName(): string;
|
|
8
|
+
abstract get(path: string, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
9
|
+
abstract post<Entity = any>(path: string, entity: Entity, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
10
|
+
abstract put<Entity = any>(path: string, entity: Entity, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
11
|
+
abstract delete(path: string, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
12
|
+
addClientToUserAgent(userAgent: string): string;
|
|
13
|
+
static getResourceURL(baseURL: string, path: string, params?: Record<string, any>): string;
|
|
14
|
+
static getQueryString(queryObj?: Record<string, any>): string | undefined;
|
|
15
|
+
static getContentTypeHeader(entity: any): RequestHeaders | undefined;
|
|
16
|
+
static getBody(entity: any): BodyInit | null | undefined;
|
|
17
|
+
}
|
|
18
|
+
export declare abstract class HttpClientResponse implements HttpClientResponseInterface {
|
|
19
|
+
_statusCode: number;
|
|
20
|
+
_headers: ResponseHeaders;
|
|
21
|
+
constructor(statusCode: number, headers: ResponseHeaders);
|
|
22
|
+
getStatusCode(): number;
|
|
23
|
+
getHeaders(): ResponseHeaders;
|
|
24
|
+
abstract getRawResponse(): unknown;
|
|
25
|
+
abstract toJSON(): any | null;
|
|
26
|
+
}
|
|
27
|
+
export declare class HttpClientError<T> extends Error {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly message: string;
|
|
30
|
+
readonly response: {
|
|
31
|
+
status: number;
|
|
32
|
+
headers: any;
|
|
33
|
+
data: T;
|
|
34
|
+
};
|
|
35
|
+
constructor({ message, response, }: {
|
|
36
|
+
message: string;
|
|
37
|
+
readonly response: HttpClientError<T>['response'];
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpClientError = exports.HttpClientResponse = exports.HttpClient = void 0;
|
|
4
|
+
class HttpClient {
|
|
5
|
+
constructor(baseURL, options) {
|
|
6
|
+
this.baseURL = baseURL;
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
/** The HTTP client name used for diagnotics */
|
|
10
|
+
getClientName() {
|
|
11
|
+
throw new Error('getClientName not implemented');
|
|
12
|
+
}
|
|
13
|
+
addClientToUserAgent(userAgent) {
|
|
14
|
+
if (userAgent.indexOf(' ') > -1) {
|
|
15
|
+
return userAgent.replace(/\b\s/, `/${this.getClientName()} `);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return (userAgent += `/${this.getClientName()}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
static getResourceURL(baseURL, path, params) {
|
|
22
|
+
const queryString = HttpClient.getQueryString(params);
|
|
23
|
+
const url = new URL([path, queryString].filter(Boolean).join('?'), baseURL);
|
|
24
|
+
return url.toString();
|
|
25
|
+
}
|
|
26
|
+
static getQueryString(queryObj) {
|
|
27
|
+
if (!queryObj)
|
|
28
|
+
return undefined;
|
|
29
|
+
const sanitizedQueryObj = {};
|
|
30
|
+
Object.entries(queryObj).forEach(([param, value]) => {
|
|
31
|
+
if (value !== '' && value !== undefined)
|
|
32
|
+
sanitizedQueryObj[param] = value;
|
|
33
|
+
});
|
|
34
|
+
return new URLSearchParams(sanitizedQueryObj).toString();
|
|
35
|
+
}
|
|
36
|
+
static getContentTypeHeader(entity) {
|
|
37
|
+
if (entity instanceof URLSearchParams) {
|
|
38
|
+
return {
|
|
39
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
static getBody(entity) {
|
|
45
|
+
if (entity === null || entity instanceof URLSearchParams) {
|
|
46
|
+
return entity;
|
|
47
|
+
}
|
|
48
|
+
return JSON.stringify(entity);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.HttpClient = HttpClient;
|
|
52
|
+
// tslint:disable-next-line
|
|
53
|
+
class HttpClientResponse {
|
|
54
|
+
constructor(statusCode, headers) {
|
|
55
|
+
this._statusCode = statusCode;
|
|
56
|
+
this._headers = headers;
|
|
57
|
+
}
|
|
58
|
+
getStatusCode() {
|
|
59
|
+
return this._statusCode;
|
|
60
|
+
}
|
|
61
|
+
getHeaders() {
|
|
62
|
+
return this._headers;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.HttpClientResponse = HttpClientResponse;
|
|
66
|
+
// tslint:disable-next-line
|
|
67
|
+
class HttpClientError extends Error {
|
|
68
|
+
constructor({ message, response, }) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = 'HttpClientError';
|
|
71
|
+
this.message = 'The request could not be completed.';
|
|
72
|
+
this.message = message;
|
|
73
|
+
this.response = response;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.HttpClientError = HttpClientError;
|
|
@@ -0,0 +1,31 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.createHttpClient = void 0;
|
|
18
|
+
const fetch_client_1 = require("./fetch-client");
|
|
19
|
+
const node_client_1 = require("./node-client");
|
|
20
|
+
function createHttpClient(baseURL, options, fetchFn) {
|
|
21
|
+
if (typeof fetch !== 'undefined' || typeof fetchFn !== 'undefined') {
|
|
22
|
+
return new fetch_client_1.FetchHttpClient(baseURL, options, fetchFn);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return new node_client_1.NodeHttpClient(baseURL, options);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.createHttpClient = createHttpClient;
|
|
29
|
+
__exportStar(require("./fetch-client"), exports);
|
|
30
|
+
__exportStar(require("./node-client"), exports);
|
|
31
|
+
__exportStar(require("./http-client"), exports);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { HttpClient, HttpClientResponse } from './http-client';
|
|
3
|
+
import { HttpClientInterface, HttpClientResponseInterface, RequestOptions } from '../interfaces/http-client.interface';
|
|
4
|
+
import * as http_ from 'http';
|
|
5
|
+
export declare class NodeHttpClient extends HttpClient implements HttpClientInterface {
|
|
6
|
+
readonly baseURL: string;
|
|
7
|
+
readonly options?: RequestInit | undefined;
|
|
8
|
+
private httpAgent;
|
|
9
|
+
private httpsAgent;
|
|
10
|
+
constructor(baseURL: string, options?: RequestInit | undefined);
|
|
11
|
+
getClientName(): string;
|
|
12
|
+
get(path: string, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
13
|
+
post<Entity = any>(path: string, entity: Entity, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
14
|
+
put<Entity = any>(path: string, entity: Entity, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
15
|
+
delete(path: string, options: RequestOptions): Promise<HttpClientResponseInterface>;
|
|
16
|
+
private nodeRequest;
|
|
17
|
+
}
|
|
18
|
+
export declare class NodeHttpClientResponse extends HttpClientResponse implements HttpClientResponseInterface {
|
|
19
|
+
_res: http_.IncomingMessage;
|
|
20
|
+
constructor(res: http_.IncomingMessage);
|
|
21
|
+
getRawResponse(): http_.IncomingMessage;
|
|
22
|
+
toJSON(): Promise<any> | any;
|
|
23
|
+
}
|
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
import { Event } from '../common/interfaces';
|
|
2
2
|
export declare class Webhooks {
|
|
3
|
-
private
|
|
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
|
}
|
package/lib/webhooks/webhooks.js
CHANGED
|
@@ -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
|
-
|
|
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)) ===
|
|
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
|
-
|
|
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
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
|
|
27
|
-
const VERSION = '7.
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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: {
|
package/lib/workos.spec.js
CHANGED
|
@@ -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
|
|
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 })).
|
|
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.
|
|
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": ">=
|
|
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
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.FetchClient = void 0;
|
|
13
|
-
const fetch_error_1 = require("./fetch-error");
|
|
14
|
-
class FetchClient {
|
|
15
|
-
constructor(baseURL, options) {
|
|
16
|
-
this.baseURL = baseURL;
|
|
17
|
-
this.options = options;
|
|
18
|
-
}
|
|
19
|
-
get(path, options) {
|
|
20
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
-
const resourceURL = this.getResourceURL(path, options.params);
|
|
22
|
-
return yield this.fetch(resourceURL, {
|
|
23
|
-
headers: options.headers,
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
post(path, entity, options) {
|
|
28
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
-
const resourceURL = this.getResourceURL(path, options.params);
|
|
30
|
-
return yield this.fetch(resourceURL, {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
headers: Object.assign(Object.assign({}, getContentTypeHeader(entity)), options.headers),
|
|
33
|
-
body: getBody(entity),
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
put(path, entity, options) {
|
|
38
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
-
const resourceURL = this.getResourceURL(path, options.params);
|
|
40
|
-
return yield this.fetch(resourceURL, {
|
|
41
|
-
method: 'PUT',
|
|
42
|
-
headers: Object.assign(Object.assign({}, getContentTypeHeader(entity)), options.headers),
|
|
43
|
-
body: getBody(entity),
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
delete(path, options) {
|
|
48
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
-
const resourceURL = this.getResourceURL(path, options.params);
|
|
50
|
-
return yield this.fetch(resourceURL, {
|
|
51
|
-
method: 'DELETE',
|
|
52
|
-
headers: options.headers,
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
getResourceURL(path, params) {
|
|
57
|
-
const queryString = getQueryString(params);
|
|
58
|
-
const url = new URL([path, queryString].filter(Boolean).join('?'), this.baseURL);
|
|
59
|
-
return url.toString();
|
|
60
|
-
}
|
|
61
|
-
fetch(url, options) {
|
|
62
|
-
var _a;
|
|
63
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
-
const response = yield fetch(url, Object.assign(Object.assign(Object.assign({}, this.options), options), { headers: Object.assign(Object.assign({ Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json' }, (_a = this.options) === null || _a === void 0 ? void 0 : _a.headers), options === null || options === void 0 ? void 0 : options.headers) }));
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
throw new fetch_error_1.FetchError({
|
|
67
|
-
message: response.statusText,
|
|
68
|
-
response: {
|
|
69
|
-
status: response.status,
|
|
70
|
-
headers: response.headers,
|
|
71
|
-
data: yield response.json(),
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const contentType = response.headers.get('content-type');
|
|
76
|
-
const isJsonResponse = contentType === null || contentType === void 0 ? void 0 : contentType.includes('application/json');
|
|
77
|
-
if (isJsonResponse) {
|
|
78
|
-
return { data: yield response.json() };
|
|
79
|
-
}
|
|
80
|
-
return { data: null };
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
exports.FetchClient = FetchClient;
|
|
85
|
-
function getQueryString(queryObj) {
|
|
86
|
-
if (!queryObj)
|
|
87
|
-
return undefined;
|
|
88
|
-
const sanitizedQueryObj = {};
|
|
89
|
-
Object.entries(queryObj).forEach(([param, value]) => {
|
|
90
|
-
if (value !== '' && value !== undefined)
|
|
91
|
-
sanitizedQueryObj[param] = value;
|
|
92
|
-
});
|
|
93
|
-
return new URLSearchParams(sanitizedQueryObj).toString();
|
|
94
|
-
}
|
|
95
|
-
function getContentTypeHeader(entity) {
|
|
96
|
-
if (entity instanceof URLSearchParams) {
|
|
97
|
-
return {
|
|
98
|
-
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return undefined;
|
|
102
|
-
}
|
|
103
|
-
function getBody(entity) {
|
|
104
|
-
if (entity === null || entity instanceof URLSearchParams) {
|
|
105
|
-
return entity;
|
|
106
|
-
}
|
|
107
|
-
return JSON.stringify(entity);
|
|
108
|
-
}
|