@oapiex/sdk-kit 0.1.1

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/dist/index.js ADDED
@@ -0,0 +1,756 @@
1
+ import "dotenv/config";
2
+ import axios from "axios";
3
+ import path from "path";
4
+ import crypto from "crypto";
5
+
6
+ //#region src/utilities/global.ts
7
+ String.prototype.toKebabCase = function() {
8
+ return this.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
9
+ };
10
+ String.prototype.toCamelCase = function() {
11
+ return this.replace(/[-_ ]+([a-zA-Z0-9])/g, (_, c) => c.toUpperCase()).replace(/^[A-Z]/, (c) => c.toLowerCase());
12
+ };
13
+ String.prototype.toPascalCase = function() {
14
+ return this.replace(/(^\w|[-_ ]+\w)/g, (match) => match.replace(/[-_ ]+/, "").toUpperCase());
15
+ };
16
+ String.prototype.toSnakeCase = function() {
17
+ return this.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
18
+ };
19
+ String.prototype.toSlug = function(separator = "-") {
20
+ return this.toSnakeCase().replace(/[/__:]+/g, "_").replace(/_/g, separator);
21
+ };
22
+ String.prototype.toTitleCase = function() {
23
+ return this.toLowerCase().replace(/(^|\s)\w/g, (match) => match.toUpperCase());
24
+ };
25
+ String.prototype.toCleanCase = function() {
26
+ return this.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ").trim().replace(/\b\w{1,3}\b/g, (match) => match.toUpperCase());
27
+ };
28
+ String.prototype.truncate = function(n, suffix = "…") {
29
+ return this.length > n ? this.slice(0, n - 1) + suffix : this.toString();
30
+ };
31
+
32
+ //#endregion
33
+ //#region src/utilities/helpers.ts
34
+ /**
35
+ * Takes a value like the one prodvided in a destructuring assignment where for instance
36
+ * a single value might have been provided as opposed to an array of values and
37
+ * normalizes it.
38
+ *
39
+ * Examples:
40
+ * Definition:
41
+ * (...value: string[]) => normalizeValue(value)
42
+ *
43
+ * Usage:
44
+ * normalizeValue('singleValue') // returns ['singleValue']
45
+ * normalizeValue('value1', 'value2') // returns ['value1', 'value2']
46
+ * normalizeValue(['value1', 'value2']) // returns ['value1', 'value2']
47
+ *
48
+ * @param value
49
+ * @returns
50
+ */
51
+ const normalizeValue = (value) => {
52
+ if (Array.isArray(value)) return value;
53
+ return [value];
54
+ };
55
+ /**
56
+ * Builds a full url based on endpoint provided
57
+ *
58
+ * @param baseUrl
59
+ * @param endpoint
60
+ *
61
+ * @returns
62
+ */
63
+ const buildUrl = (baseUrl, ...endpoint) => {
64
+ return (baseUrl + path.normalize(path.join(...normalizeValue(endpoint)))).replace(/([^:]\/)\/+/g, "$1");
65
+ };
66
+
67
+ //#endregion
68
+ //#region src/Builder.ts
69
+ var Builder = class {
70
+ static baseUrls = {
71
+ live: "https://api.flutterwave.com/v4/",
72
+ sandbox: "https://developersandbox-api.flutterwave.com/"
73
+ };
74
+ /**
75
+ * Flutterwave Environment
76
+ */
77
+ static environment;
78
+ constructor() {}
79
+ /**
80
+ * Sets the environment for the builder
81
+ *
82
+ * @param env
83
+ */
84
+ static setEnvironment(env) {
85
+ this.environment = env;
86
+ }
87
+ /**
88
+ * Gets the base url based on environment
89
+ *
90
+ * @returns
91
+ */
92
+ static baseUrl() {
93
+ if ((process.env.ENVIRONMENT || this.environment || "sandbox") === "live") return this.baseUrls.live;
94
+ return this.baseUrls.sandbox;
95
+ }
96
+ /**
97
+ * Builds a full url based on endpoint provided
98
+ *
99
+ * @param endpoint
100
+ * @returns
101
+ */
102
+ static buildUrl(...endpoint) {
103
+ return buildUrl(this.baseUrl(), ...endpoint);
104
+ }
105
+ /**
106
+ * Builds parameters for query or path
107
+ *
108
+ * @param params
109
+ * @param type
110
+ * @returns
111
+ */
112
+ static buildParams(params, type = "path") {
113
+ let built = "";
114
+ if (type === "path") built = Object.values(params).join("/");
115
+ else {
116
+ const queryParams = [];
117
+ for (const [key, value] of Object.entries(params)) queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
118
+ built = queryParams.join("&");
119
+ }
120
+ return built;
121
+ }
122
+ /**
123
+ * Assigns parameters to url {placeholders} based on type
124
+ *
125
+ * @param url
126
+ * @param params
127
+ * @param type
128
+ *
129
+ * @returns
130
+ */
131
+ static assignParamsToUrl(url, params, type = "path") {
132
+ let builtUrl = url;
133
+ if (type === "path") for (const [key, value] of Object.entries(params)) {
134
+ builtUrl = builtUrl.replace(`{${key}}`, encodeURIComponent(String(value)));
135
+ builtUrl = builtUrl.replace(`:${key}`, encodeURIComponent(String(value)));
136
+ }
137
+ else {
138
+ if (Object.keys(params).length === 0) return builtUrl;
139
+ const queryString = this.buildParams(params, "query");
140
+ const separator = builtUrl.includes("?") ? "&" : "?";
141
+ builtUrl += `${separator}${queryString}`;
142
+ }
143
+ return builtUrl;
144
+ }
145
+ /**
146
+ * Builds the target url by assigning both path and query parameters
147
+ *
148
+ * @param path
149
+ * @param params
150
+ * @param queryParams
151
+ * @returns
152
+ */
153
+ static buildTargetUrl(path$1, params = {}, queryParams = {}) {
154
+ const url = this.buildUrl(path$1);
155
+ let builtUrl = this.assignParamsToUrl(url, params, "path");
156
+ builtUrl = this.assignParamsToUrl(builtUrl, queryParams, "query");
157
+ return builtUrl;
158
+ }
159
+ /**
160
+ * Encrypts specified keys in the input object and returns a new object with
161
+ * encrypted values.
162
+ *
163
+ * @param input The input object containing the data to be encrypted.
164
+ * @param keysToEncrypt The keys in the input object that should be encrypted.
165
+ * @param outputMapping A mapping of input keys to output keys for the encrypted values.
166
+ * @returns A new object with the specified keys encrypted.
167
+ */
168
+ static async encryptDetails(input, keysToEncrypt = [], outputMapping = {}) {
169
+ const nonce = crypto.randomBytes(12).toString("base64").slice(0, 12);
170
+ const encryptableKeys = keysToEncrypt.length > 0 ? keysToEncrypt : Object.keys(input);
171
+ const encrypted = Object.fromEntries(Object.entries(input).map(([key, value]) => {
172
+ if (encryptableKeys.includes(key) && typeof value === "string") return [outputMapping?.[key] || key, this.encryptAES(value, process.env.ENCRYPTION_KEY, nonce)];
173
+ return [key, value];
174
+ }));
175
+ for (const key of encryptableKeys) delete input[key];
176
+ return encrypted;
177
+ }
178
+ /**
179
+ * Encrypts data using AES-GCM encryption
180
+ * @param data
181
+ * @param token
182
+ * @param nonce
183
+ * @returns
184
+ */
185
+ static async encryptAES(data, token, nonce) {
186
+ if (nonce.length !== 12) throw new Error("Nonce must be exactly 12 characters long");
187
+ const cryptoSubtle = globalThis.crypto?.subtle || crypto.webcrypto?.subtle;
188
+ if (!cryptoSubtle) throw new Error("Crypto API is not available in this environment.");
189
+ const decodedKeyBytes = Uint8Array.from(atob(token), (c) => c.charCodeAt(0));
190
+ const key = await cryptoSubtle.importKey("raw", decodedKeyBytes, { name: "AES-GCM" }, false, ["encrypt"]);
191
+ const iv = new TextEncoder().encode(nonce);
192
+ const encryptedData = await cryptoSubtle.encrypt({
193
+ name: "AES-GCM",
194
+ iv
195
+ }, key, new TextEncoder().encode(data));
196
+ return btoa(String.fromCharCode(...new Uint8Array(encryptedData)));
197
+ }
198
+ };
199
+
200
+ //#endregion
201
+ //#region src/Exceptions/BadRequestException.ts
202
+ var BadRequestException = class extends Error {
203
+ statusCode = 400;
204
+ type = "INVALID_REQUEST";
205
+ code = "400";
206
+ data;
207
+ constructor(data) {
208
+ super(data?.error.message ?? data?.message ?? "Bad request. The server could not understand the request due to invalid syntax.");
209
+ if (data?.error.code) this.type = data.error.type;
210
+ if (data?.error.code) this.code = data.error.code;
211
+ this.data = data ?? {
212
+ status: "failed",
213
+ error: {
214
+ type: this.type,
215
+ code: this.code,
216
+ message: this.message,
217
+ validation_errors: []
218
+ }
219
+ };
220
+ this.name = "BadRequestException";
221
+ }
222
+ };
223
+
224
+ //#endregion
225
+ //#region src/Exceptions/ForbiddenRequestException.ts
226
+ var ForbiddenRequestException = class extends Error {
227
+ statusCode = 403;
228
+ type = "FORBIDDEN";
229
+ code = "403";
230
+ data;
231
+ constructor(data) {
232
+ super(data?.error.message ?? data?.message ?? "Forbidden request. You do not have permission to access this resource.");
233
+ if (data?.error.code) this.type = data.error.type;
234
+ if (data?.error.code) this.code = data.error.code;
235
+ this.data = data ?? {
236
+ status: "failed",
237
+ error: {
238
+ type: this.type,
239
+ code: this.code,
240
+ message: this.message
241
+ }
242
+ };
243
+ this.name = "ForbiddenRequestException";
244
+ }
245
+ };
246
+
247
+ //#endregion
248
+ //#region src/Exceptions/UnauthorizedRequestException.ts
249
+ var UnauthorizedRequestException = class extends Error {
250
+ statusCode = 401;
251
+ type = "UNAUTHORIZED";
252
+ code = "401";
253
+ data;
254
+ constructor(data) {
255
+ super(data?.error.message ?? data?.message ?? "Forbidden request. You do not have permission to access this resource.");
256
+ if (data?.error.code) this.type = data.error.type;
257
+ if (data?.error.code) this.code = data.error.code;
258
+ this.data = data ?? {
259
+ status: "failed",
260
+ error: {
261
+ type: this.type,
262
+ code: this.code,
263
+ message: this.message
264
+ }
265
+ };
266
+ this.name = "UnauthorizedRequestException";
267
+ }
268
+ };
269
+
270
+ //#endregion
271
+ //#region src/Exceptions/HttpException.ts
272
+ var HttpException = class HttpException extends Error {
273
+ statusCode = 500;
274
+ parent;
275
+ constructor(data, statusCode, parent) {
276
+ super(data.message);
277
+ this.data = data;
278
+ this.name = "HttpException";
279
+ this.parent = parent;
280
+ if (statusCode) this.statusCode = statusCode;
281
+ }
282
+ /**
283
+ * Create an exception from status code
284
+ *
285
+ * @param code
286
+ * @param data
287
+ */
288
+ static fromCode(code, data, parent) {
289
+ switch (code) {
290
+ case 400: return new BadRequestException(data);
291
+ case 401: return new UnauthorizedRequestException(data);
292
+ case 403: return new ForbiddenRequestException(data);
293
+ default: return new HttpException(data, code, parent);
294
+ }
295
+ }
296
+ };
297
+
298
+ //#endregion
299
+ //#region src/Http.ts
300
+ var Http = class Http {
301
+ /**
302
+ * Bearer token
303
+ */
304
+ static bearerToken;
305
+ /**
306
+ * Debug level
307
+ */
308
+ static debugLevel = 0;
309
+ static apiInstance;
310
+ /**
311
+ * Creates an instance of Http.
312
+ *
313
+ * @param method
314
+ * @param url
315
+ * @param headers
316
+ * @param body
317
+ */
318
+ constructor(headers = {}, body) {
319
+ this.headers = headers;
320
+ this.body = body;
321
+ }
322
+ /**
323
+ * Set the debug level
324
+ *
325
+ * @param debug
326
+ */
327
+ static setDebugLevel(level = 0) {
328
+ this.debugLevel = level ?? 0;
329
+ }
330
+ /**
331
+ * Set the API instance
332
+ *
333
+ * @param api
334
+ */
335
+ static setApiInstance(api) {
336
+ this.apiInstance = api;
337
+ }
338
+ /**
339
+ * Set the bearer token
340
+ *
341
+ * @param token
342
+ */
343
+ static setBearerToken(token) {
344
+ this.bearerToken = token;
345
+ }
346
+ setDefaultHeaders(defaults) {
347
+ this.headers = {
348
+ ...defaults,
349
+ ...this.headers
350
+ };
351
+ if (Http.bearerToken) this.headers.Authorization = `Bearer ${Http.bearerToken}`;
352
+ }
353
+ getHeaders() {
354
+ return this.headers;
355
+ }
356
+ getBody() {
357
+ return this.body;
358
+ }
359
+ axiosApi() {
360
+ this.setDefaultHeaders({
361
+ "Accept": "application/json",
362
+ "Content-Type": "application/json"
363
+ });
364
+ const instance = axios.create({
365
+ baseURL: Builder.baseUrl(),
366
+ headers: this.getHeaders()
367
+ });
368
+ if (Http.debugLevel > 0) {
369
+ instance.interceptors.request.use((request) => {
370
+ console.log("Starting Request", JSON.stringify({
371
+ url: request.url,
372
+ method: request.method,
373
+ headers: Http.debugLevel < 3 ? Object.fromEntries(Object.entries(request.headers || {}).filter(([key]) => key.toLowerCase() !== "authorization")) : request.headers,
374
+ params: request.params,
375
+ data: request.data
376
+ }, null, 2));
377
+ return request;
378
+ }, (error) => {
379
+ console.log("Request Error:", JSON.stringify(error, null, 2));
380
+ return Promise.reject(error);
381
+ });
382
+ instance.interceptors.response.use((response) => {
383
+ console.log("Response:", JSON.stringify({
384
+ status: response.status,
385
+ data: Http.debugLevel < 3 ? Object.fromEntries(Object.entries(response.data || {}).filter(([key]) => key.toLowerCase() !== "access_token")) : response.data
386
+ }, null, 2));
387
+ return response;
388
+ }, (error) => {
389
+ console.log("Response Error:", JSON.stringify({
390
+ status: error.response?.status,
391
+ data: Object.fromEntries(Object.entries(error.response?.data || {}).filter(([key]) => key.toLowerCase() !== "access_token"))
392
+ }, null, 2));
393
+ return Promise.reject(error);
394
+ });
395
+ }
396
+ return instance;
397
+ }
398
+ /**
399
+ * Makes a GET request
400
+ *
401
+ * @param url
402
+ * @param headers
403
+ * @param params
404
+ * @returns
405
+ */
406
+ static async get(url, params, headers = {}) {
407
+ return this.send(url, "GET", void 0, headers, params);
408
+ }
409
+ /**
410
+ *
411
+ *
412
+ * @param url
413
+ * @param headers
414
+ * @param params
415
+ * @returns
416
+ */
417
+ static async send(url, method, body, headers = {}, params) {
418
+ try {
419
+ const { data } = await new Http(headers).axiosApi()({
420
+ url,
421
+ method,
422
+ data: body,
423
+ params
424
+ });
425
+ return {
426
+ success: true,
427
+ message: data.message || "Request successful",
428
+ data: data.data ?? data,
429
+ meta: data.meta
430
+ };
431
+ } catch (e) {
432
+ const error = e.response?.data ?? {};
433
+ throw this.exception(e.response?.status ?? 500, error || e, e);
434
+ }
435
+ }
436
+ /**
437
+ * Create an HttpException from status and error
438
+ *
439
+ * @param status
440
+ * @param error
441
+ * @returns
442
+ */
443
+ static exception(status, error, originalError) {
444
+ const exception = HttpException.fromCode(status, {
445
+ success: false,
446
+ message: `Request failed: ${error.error?.message || "An error occurred"}`,
447
+ status: "failed",
448
+ data: void 0,
449
+ meta: {},
450
+ error: {
451
+ type: ((typeof error.error === "string" ? error.error : error.error?.type) ?? "UNKNOWN_ERROR").toUpperCase(),
452
+ code: error.error?.code ?? "000000",
453
+ message: error.error?.message ?? error.error_description ?? error.message,
454
+ validation_errors: error.error?.validation_errors ?? []
455
+ }
456
+ }, originalError);
457
+ if (this.apiInstance) this.apiInstance.setLastException(exception);
458
+ return exception;
459
+ }
460
+ };
461
+
462
+ //#endregion
463
+ //#region src/Apis/BaseApi.ts
464
+ var BaseApi = class {
465
+ /**
466
+ * Flutterwave instance
467
+ */
468
+ #core;
469
+ lastException;
470
+ /**
471
+ * Create a BaseApi instance
472
+ *
473
+ * @param coreInstance Core instance
474
+ */
475
+ constructor(coreInstance) {
476
+ this.#core = coreInstance;
477
+ }
478
+ /**
479
+ * Get the owning core instance for SDK-specific API bootstrapping.
480
+ */
481
+ get core() {
482
+ return this.#core;
483
+ }
484
+ /**
485
+ * Hook for SDK-specific API registration.
486
+ */
487
+ boot() {}
488
+ /**
489
+ * Set access validator function
490
+ *
491
+ * @param validator
492
+ */
493
+ setAccessValidator(validator) {
494
+ this.#core.setAccessValidator(validator);
495
+ }
496
+ /**
497
+ * Get the last exception
498
+ *
499
+ * @returns
500
+ */
501
+ getLastException() {
502
+ return this.lastException;
503
+ }
504
+ /**
505
+ * Set the last exception
506
+ *
507
+ * @param exception
508
+ */
509
+ setLastException(exception) {
510
+ this.lastException = exception;
511
+ }
512
+ /**
513
+ * Initialize BaseApi and its sub-APIs
514
+ *
515
+ * @param coreInstance Core instance
516
+ * @returns
517
+ */
518
+ static initialize(coreInstance) {
519
+ Http.setDebugLevel(coreInstance.debugLevel);
520
+ const baseApi = new this(coreInstance);
521
+ Http.setApiInstance(baseApi);
522
+ baseApi.boot();
523
+ return baseApi;
524
+ }
525
+ };
526
+
527
+ //#endregion
528
+ //#region src/RuntimeSdk.ts
529
+ const createRuntimeApi = (core, bundle) => {
530
+ const api = BaseApi.initialize(core);
531
+ for (const group of bundle.manifest.groups) {
532
+ const namespace = {};
533
+ for (const operation of group.operations) namespace[operation.methodName] = async (...args) => {
534
+ await core.validateAccess();
535
+ const [pathParams, queryParams, body, headers] = normalizeRuntimeArguments(operation, args);
536
+ validateRequiredArguments(operation.pathParams, pathParams, "path");
537
+ validateRequiredArguments(operation.queryParams, queryParams, "query");
538
+ validateRequiredArguments(operation.headerParams, headers, "header");
539
+ if (operation.hasBody && operation.bodyRequired && body == null) throw new Error(`Missing required request body for ${operation.method} ${operation.path}`);
540
+ const { data } = await Http.send(core.builder.buildTargetUrl(operation.path, pathParams, queryParams), operation.method, body ?? {}, headers);
541
+ return data;
542
+ };
543
+ api[group.propertyName] = namespace;
544
+ }
545
+ return api;
546
+ };
547
+ const createSdk = (bundle, options) => {
548
+ return new Core(options).useDocument(bundle);
549
+ };
550
+ const normalizeRuntimeArguments = (operation, args) => {
551
+ let cursor = 0;
552
+ return [
553
+ operation.pathParams.length > 0 ? toRecord(args[cursor++]) : {},
554
+ operation.queryParams.length > 0 ? toRecord(args[cursor++]) : {},
555
+ operation.hasBody ? args[cursor++] : void 0,
556
+ operation.headerParams.length > 0 ? toRecord(args[cursor++]) : {}
557
+ ];
558
+ };
559
+ const validateRequiredArguments = (parameters, values, location) => {
560
+ for (const parameter of parameters) {
561
+ if (!parameter.required) continue;
562
+ if (values[parameter.name] === void 0 && values[parameter.accessor] === void 0) throw new Error(`Missing required ${location} parameter: ${parameter.name}`);
563
+ }
564
+ };
565
+ const toRecord = (value) => {
566
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
567
+ return {};
568
+ };
569
+
570
+ //#endregion
571
+ //#region src/Core.ts
572
+ var Core = class {
573
+ static apiClass = BaseApi;
574
+ debugLevel = 0;
575
+ /**
576
+ * Client ID
577
+ */
578
+ clientId;
579
+ /**
580
+ * Client Secret
581
+ */
582
+ clientSecret;
583
+ /**
584
+ * Flutterwave Environment
585
+ */
586
+ environment = "live";
587
+ accessValidator;
588
+ /**
589
+ * API Instance
590
+ */
591
+ api;
592
+ /**
593
+ * Builder Instance
594
+ */
595
+ builder = Builder;
596
+ constructor(clientId, clientSecret, encryptionKey, env) {
597
+ if (typeof clientId === "object") {
598
+ this.clientId = clientId.clientId;
599
+ this.clientSecret = clientId.clientSecret;
600
+ this.environment = clientId.environment ?? "live";
601
+ } else {
602
+ this.clientId = clientId ?? process.env.CLIENT_ID ?? "";
603
+ this.clientSecret = clientSecret ?? process.env.CLIENT_SECRET ?? "";
604
+ this.environment = env ?? process.env.ENVIRONMENT ?? "live";
605
+ }
606
+ if (!this.clientId || !this.clientSecret) throw new Error("Client ID and Client Secret are required to initialize Flutterwave instance");
607
+ this.builder.setEnvironment(this.environment);
608
+ this.api = this.createApi();
609
+ }
610
+ createApi() {
611
+ return (this.constructor.apiClass ?? BaseApi).initialize(this);
612
+ }
613
+ init(clientId, clientSecret, encryptionKey, env) {
614
+ return new this.constructor(clientId, clientSecret, encryptionKey, env);
615
+ }
616
+ /**
617
+ * Set the debug level
618
+ *
619
+ * @param level
620
+ * @returns
621
+ */
622
+ debug(level = 0) {
623
+ this.debugLevel = level;
624
+ Http.setDebugLevel(level);
625
+ return this;
626
+ }
627
+ /**
628
+ * Get the current environment
629
+ *
630
+ * @returns
631
+ */
632
+ getEnvironment() {
633
+ return this.environment;
634
+ }
635
+ /**
636
+ * Set access validator function
637
+ *
638
+ * @param validator Function to validate access
639
+ */
640
+ setAccessValidator(validator) {
641
+ this.accessValidator = validator;
642
+ }
643
+ /**
644
+ * Validates access using the provided access validator function
645
+ *
646
+ * @throws Error if validation fails
647
+ */
648
+ async validateAccess() {
649
+ const check = this.accessValidator ? await this.accessValidator(this) : true;
650
+ if (check !== true) throw new Error(typeof check === "string" ? check : "Access validation failed");
651
+ }
652
+ /**
653
+ * Use a manifest bundle to create the API instance
654
+ *
655
+ * @param bundle
656
+ * @returns
657
+ */
658
+ useDocument(bundle) {
659
+ this.api = createRuntimeApi(this, bundle);
660
+ return this;
661
+ }
662
+ /**
663
+ * Use a manifest bundle to create the API instance (alias for useDocument).
664
+ *
665
+ * @param bundle
666
+ * @returns
667
+ */
668
+ useSdk(bundle) {
669
+ return this.useDocument(bundle);
670
+ }
671
+ };
672
+
673
+ //#endregion
674
+ //#region src/utilities/Manager.ts
675
+ const defaultConfig = {
676
+ environment: "sandbox",
677
+ urls: {
678
+ live: "",
679
+ sandbox: ""
680
+ }
681
+ };
682
+ let globalConfig = defaultConfig;
683
+ const defineConfig = (config) => {
684
+ const userConfig = {
685
+ ...defaultConfig,
686
+ ...config,
687
+ urls: {
688
+ ...defaultConfig.urls,
689
+ ...config.urls
690
+ }
691
+ };
692
+ globalConfig = userConfig;
693
+ return userConfig;
694
+ };
695
+
696
+ //#endregion
697
+ //#region src/utilities/WebhookValidator.ts
698
+ var WebhookValidator = class {
699
+ algorithm = "sha256";
700
+ encoding = "base64";
701
+ /**
702
+ * @param secretHash
703
+ * @param options
704
+ */
705
+ constructor(secretHash, options) {
706
+ this.secretHash = secretHash;
707
+ this.algorithm = options?.hashAlgorithm || "sha256";
708
+ this.encoding = options?.encoding || "base64";
709
+ if (!this.secretHash || this.secretHash.length === 0) this.secretHash = process.env.SECRET_HASH;
710
+ }
711
+ /**
712
+ * Validate webhook signature
713
+ * @param rawBody - Raw request body as string
714
+ * @param signature - Signature from request header
715
+ * @returns {boolean} - True if signature is valid
716
+ */
717
+ validate(rawBody, signature) {
718
+ if (!this.secretHash) throw new Error("Secret hash is required for validation");
719
+ return crypto.createHmac(this.algorithm, this.secretHash).update(rawBody).digest(this.encoding) === signature;
720
+ }
721
+ /**
722
+ * Generate signature for testing/verification
723
+ * @param rawBody - Raw request body as string
724
+ * @returns {string} - Generated signature
725
+ */
726
+ generateSignature(rawBody) {
727
+ if (!this.secretHash) throw new Error("Secret hash is required to generate signature");
728
+ return crypto.createHmac(this.algorithm, this.secretHash).update(rawBody).digest(this.encoding);
729
+ }
730
+ /**
731
+ * Async version of validate (for large payloads)
732
+ *
733
+ * @param rawBody
734
+ * @param signature
735
+ * @returns {Promise<boolean>}
736
+ */
737
+ validateAsync(rawBody, signature) {
738
+ if (!this.secretHash) throw new Error("Secret hash is required for validation");
739
+ const hmac = crypto.createHmac(this.algorithm, this.secretHash);
740
+ hmac.update(rawBody);
741
+ return hmac.digest(this.encoding) === signature;
742
+ }
743
+ /**
744
+ * Get current configuration
745
+ */
746
+ getConfig() {
747
+ return {
748
+ algorithm: this.algorithm,
749
+ encoding: this.encoding,
750
+ secretHashLength: this.secretHash?.length
751
+ };
752
+ }
753
+ };
754
+
755
+ //#endregion
756
+ export { BadRequestException, BaseApi, Builder, Core, ForbiddenRequestException, Http, HttpException, UnauthorizedRequestException, WebhookValidator, buildUrl, createRuntimeApi, createSdk, defaultConfig, defineConfig, globalConfig, normalizeValue };