@scryme/chat 2.91.61 → 2.91.87
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/custom-instance.d.ts +22 -2
- package/dist/custom-instance.js +109 -1
- package/dist/sdk.js +12 -4
- package/package.json +1 -1
- package/src/__tests__/sdk-error.test.ts +229 -0
- package/src/custom-instance.ts +109 -2
- package/src/sdk.ts +7 -3
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import { AxiosRequestConfig
|
|
1
|
+
import { AxiosRequestConfig } from 'axios';
|
|
2
2
|
export declare const setGlobalToken: (token: string | null) => void;
|
|
3
3
|
export declare const getGlobalToken: () => string | null;
|
|
4
|
+
/**
|
|
5
|
+
* Custom SDK Error class for meaningful, developer-friendly error messages and structured error details.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ScrymeSDKError extends Error {
|
|
8
|
+
readonly status?: number;
|
|
9
|
+
readonly code?: string;
|
|
10
|
+
readonly data?: any;
|
|
11
|
+
readonly rawError?: any;
|
|
12
|
+
constructor(message: string, options?: {
|
|
13
|
+
status?: number;
|
|
14
|
+
code?: string;
|
|
15
|
+
data?: any;
|
|
16
|
+
rawError?: any;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export { ScrymeSDKError as ScrymeError };
|
|
20
|
+
/**
|
|
21
|
+
* Parses any error (AxiosError, Error, or unknown object) into a clean, meaningful ScrymeSDKError.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseSDKError(error: any): ScrymeSDKError;
|
|
4
24
|
export declare const AXIOS_INSTANCE: import("axios").AxiosInstance;
|
|
5
25
|
export declare const customInstance: <T>(config: AxiosRequestConfig, options?: AxiosRequestConfig) => Promise<T>;
|
|
6
|
-
export type ErrorType<Error> =
|
|
26
|
+
export type ErrorType<Error> = ScrymeSDKError;
|
|
7
27
|
export type BodyType<Body> = Body;
|
package/dist/custom-instance.js
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
var __extends = (this && this.__extends) || (function () {
|
|
2
|
+
var extendStatics = function (d, b) {
|
|
3
|
+
extendStatics = Object.setPrototypeOf ||
|
|
4
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
5
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
6
|
+
return extendStatics(d, b);
|
|
7
|
+
};
|
|
8
|
+
return function (d, b) {
|
|
9
|
+
if (typeof b !== "function" && b !== null)
|
|
10
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
11
|
+
extendStatics(d, b);
|
|
12
|
+
function __() { this.constructor = d; }
|
|
13
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
14
|
+
};
|
|
15
|
+
})();
|
|
1
16
|
var __assign = (this && this.__assign) || function () {
|
|
2
17
|
__assign = Object.assign || function(t) {
|
|
3
18
|
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
@@ -46,11 +61,100 @@ export var setGlobalToken = function (token) {
|
|
|
46
61
|
globalToken = token;
|
|
47
62
|
};
|
|
48
63
|
export var getGlobalToken = function () { return globalToken; };
|
|
64
|
+
/**
|
|
65
|
+
* Custom SDK Error class for meaningful, developer-friendly error messages and structured error details.
|
|
66
|
+
*/
|
|
67
|
+
var ScrymeSDKError = /** @class */ (function (_super) {
|
|
68
|
+
__extends(ScrymeSDKError, _super);
|
|
69
|
+
function ScrymeSDKError(message, options) {
|
|
70
|
+
var _newTarget = this.constructor;
|
|
71
|
+
var _this = _super.call(this, message) || this;
|
|
72
|
+
_this.name = 'ScrymeSDKError';
|
|
73
|
+
_this.status = options === null || options === void 0 ? void 0 : options.status;
|
|
74
|
+
_this.code = options === null || options === void 0 ? void 0 : options.code;
|
|
75
|
+
_this.data = options === null || options === void 0 ? void 0 : options.data;
|
|
76
|
+
_this.rawError = options === null || options === void 0 ? void 0 : options.rawError;
|
|
77
|
+
Object.setPrototypeOf(_this, _newTarget.prototype);
|
|
78
|
+
return _this;
|
|
79
|
+
}
|
|
80
|
+
return ScrymeSDKError;
|
|
81
|
+
}(Error));
|
|
82
|
+
export { ScrymeSDKError };
|
|
83
|
+
export { ScrymeSDKError as ScrymeError };
|
|
84
|
+
/**
|
|
85
|
+
* Parses any error (AxiosError, Error, or unknown object) into a clean, meaningful ScrymeSDKError.
|
|
86
|
+
*/
|
|
87
|
+
export function parseSDKError(error) {
|
|
88
|
+
if (error instanceof ScrymeSDKError) {
|
|
89
|
+
return error;
|
|
90
|
+
}
|
|
91
|
+
if (axios.isAxiosError(error)) {
|
|
92
|
+
var response = error.response;
|
|
93
|
+
var status_1 = response === null || response === void 0 ? void 0 : response.status;
|
|
94
|
+
var responseData = response === null || response === void 0 ? void 0 : response.data;
|
|
95
|
+
var code = error.code || (responseData && typeof responseData === 'object' ? responseData.code : undefined);
|
|
96
|
+
var message = void 0;
|
|
97
|
+
if (responseData) {
|
|
98
|
+
if (typeof responseData === 'string' && responseData.trim()) {
|
|
99
|
+
message = responseData;
|
|
100
|
+
}
|
|
101
|
+
else if (typeof responseData === 'object' && responseData !== null) {
|
|
102
|
+
if (Array.isArray(responseData.message)) {
|
|
103
|
+
message = responseData.message.filter(Boolean).join('; ');
|
|
104
|
+
}
|
|
105
|
+
else if (typeof responseData.message === 'string' && responseData.message.trim()) {
|
|
106
|
+
message = responseData.message;
|
|
107
|
+
}
|
|
108
|
+
else if (typeof responseData.error === 'string' && responseData.error.trim()) {
|
|
109
|
+
message = responseData.error;
|
|
110
|
+
}
|
|
111
|
+
else if (responseData.error && typeof responseData.error === 'object' && typeof responseData.error.message === 'string') {
|
|
112
|
+
message = responseData.error.message;
|
|
113
|
+
}
|
|
114
|
+
else if (typeof responseData.detail === 'string' && responseData.detail.trim()) {
|
|
115
|
+
message = responseData.detail;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!message) {
|
|
120
|
+
if (response === null || response === void 0 ? void 0 : response.statusText) {
|
|
121
|
+
message = "HTTP ".concat(status_1, ": ").concat(response.statusText);
|
|
122
|
+
}
|
|
123
|
+
else if (error.message && !error.message.startsWith('Request failed with status code')) {
|
|
124
|
+
message = error.message;
|
|
125
|
+
}
|
|
126
|
+
else if (status_1) {
|
|
127
|
+
message = "Request failed with status code ".concat(status_1);
|
|
128
|
+
}
|
|
129
|
+
else if (error.message) {
|
|
130
|
+
message = error.message;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
message = 'An unexpected error occurred during API request';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return new ScrymeSDKError(message, {
|
|
137
|
+
status: status_1,
|
|
138
|
+
code: code,
|
|
139
|
+
data: responseData,
|
|
140
|
+
rawError: error,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (error instanceof Error) {
|
|
144
|
+
return new ScrymeSDKError(error.message, {
|
|
145
|
+
rawError: error,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return new ScrymeSDKError(typeof error === 'string' ? error : 'An unexpected error occurred', {
|
|
149
|
+
rawError: error,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
49
152
|
export var AXIOS_INSTANCE = axios.create({
|
|
50
153
|
baseURL: getBaseURL(),
|
|
51
154
|
timeout: 10000,
|
|
52
155
|
withCredentials: true,
|
|
53
156
|
});
|
|
157
|
+
AXIOS_INSTANCE.interceptors.response.use(function (response) { return response; }, function (error) { return Promise.reject(parseSDKError(error)); });
|
|
54
158
|
AXIOS_INSTANCE.interceptors.request.use(function (config) {
|
|
55
159
|
if (!config.baseURL) {
|
|
56
160
|
config.baseURL = getBaseURL();
|
|
@@ -118,9 +222,13 @@ export var customInstance = function (config, options) {
|
|
|
118
222
|
var source = axios.CancelToken.source();
|
|
119
223
|
// Merge headers carefully so that options.headers does not overwrite config.headers completely
|
|
120
224
|
var mergedHeaders = __assign(__assign({}, config.headers), options === null || options === void 0 ? void 0 : options.headers);
|
|
121
|
-
var promise = AXIOS_INSTANCE(__assign(__assign(__assign({}, config), options), { headers: mergedHeaders, cancelToken: source.token }))
|
|
225
|
+
var promise = AXIOS_INSTANCE(__assign(__assign(__assign({}, config), options), { headers: mergedHeaders, cancelToken: source.token }))
|
|
226
|
+
.then(function (_a) {
|
|
122
227
|
var data = _a.data;
|
|
123
228
|
return data;
|
|
229
|
+
})
|
|
230
|
+
.catch(function (error) {
|
|
231
|
+
throw parseSDKError(error);
|
|
124
232
|
});
|
|
125
233
|
// @ts-ignore
|
|
126
234
|
promise.cancel = function () {
|
package/dist/sdk.js
CHANGED
|
@@ -55,7 +55,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
55
55
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
56
56
|
};
|
|
57
57
|
import axios from 'axios';
|
|
58
|
-
import { setGlobalToken } from './custom-instance';
|
|
58
|
+
import { setGlobalToken, parseSDKError } from './custom-instance';
|
|
59
59
|
import { getSkyrmeChatAPI } from './generated/v3-server';
|
|
60
60
|
/**
|
|
61
61
|
* The primary client SDK class for accessing the Scryme Chat platform APIs.
|
|
@@ -173,7 +173,7 @@ var ScrymeSDK = /** @class */ (function () {
|
|
|
173
173
|
case 3:
|
|
174
174
|
error_1 = _e.sent();
|
|
175
175
|
console.error('ScrymeSDK failed to authenticate via client_credentials:', error_1);
|
|
176
|
-
throw error_1;
|
|
176
|
+
throw parseSDKError(error_1);
|
|
177
177
|
case 4: return [2 /*return*/, this.token];
|
|
178
178
|
}
|
|
179
179
|
});
|
|
@@ -217,7 +217,7 @@ var ScrymeSDK = /** @class */ (function () {
|
|
|
217
217
|
args[_i] = arguments[_i];
|
|
218
218
|
}
|
|
219
219
|
return __awaiter(_this, void 0, void 0, function () {
|
|
220
|
-
var config, arity, cleanArgs, lastArg, isLastArgOptions, userOptions;
|
|
220
|
+
var config, arity, cleanArgs, lastArg, isLastArgOptions, userOptions, error_2;
|
|
221
221
|
return __generator(this, function (_a) {
|
|
222
222
|
switch (_a.label) {
|
|
223
223
|
case 0: return [4 /*yield*/, this.getRequestConfig()];
|
|
@@ -242,7 +242,15 @@ var ScrymeSDK = /** @class */ (function () {
|
|
|
242
242
|
}
|
|
243
243
|
cleanArgs.push(config);
|
|
244
244
|
}
|
|
245
|
-
|
|
245
|
+
_a.label = 2;
|
|
246
|
+
case 2:
|
|
247
|
+
_a.trys.push([2, 4, , 5]);
|
|
248
|
+
return [4 /*yield*/, originalMethod.apply(void 0, cleanArgs)];
|
|
249
|
+
case 3: return [2 /*return*/, _a.sent()];
|
|
250
|
+
case 4:
|
|
251
|
+
error_2 = _a.sent();
|
|
252
|
+
throw parseSDKError(error_2);
|
|
253
|
+
case 5: return [2 /*return*/];
|
|
246
254
|
}
|
|
247
255
|
});
|
|
248
256
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import axios, { AxiosError } from 'axios';
|
|
3
|
+
import { ScrymeSDKError, ScrymeError, parseSDKError } from '../custom-instance';
|
|
4
|
+
import { ScrymeSDK } from '../sdk';
|
|
5
|
+
|
|
6
|
+
describe('ScrymeSDKError & parseSDKError', () => {
|
|
7
|
+
it('should construct ScrymeSDKError with status, code, data, and rawError', () => {
|
|
8
|
+
const rawErr = new Error('Raw');
|
|
9
|
+
const err = new ScrymeSDKError('Something went wrong', {
|
|
10
|
+
status: 400,
|
|
11
|
+
code: 'BAD_REQUEST',
|
|
12
|
+
data: { details: 'Invalid field' },
|
|
13
|
+
rawError: rawErr,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(err).toBeInstanceOf(Error);
|
|
17
|
+
expect(err).toBeInstanceOf(ScrymeSDKError);
|
|
18
|
+
expect(err).toBeInstanceOf(ScrymeError);
|
|
19
|
+
expect(err.name).toBe('ScrymeSDKError');
|
|
20
|
+
expect(err.message).toBe('Something went wrong');
|
|
21
|
+
expect(err.status).toBe(400);
|
|
22
|
+
expect(err.code).toBe('BAD_REQUEST');
|
|
23
|
+
expect(err.data).toEqual({ details: 'Invalid field' });
|
|
24
|
+
expect(err.rawError).toBe(rawErr);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return existing ScrymeSDKError as is', () => {
|
|
28
|
+
const original = new ScrymeSDKError('Already parsed');
|
|
29
|
+
const result = parseSDKError(original);
|
|
30
|
+
expect(result).toBe(original);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should parse NestJS validation error arrays into joined message', () => {
|
|
34
|
+
const axiosError = new AxiosError(
|
|
35
|
+
'Request failed with status code 400',
|
|
36
|
+
'ERR_BAD_REQUEST',
|
|
37
|
+
undefined,
|
|
38
|
+
{},
|
|
39
|
+
{
|
|
40
|
+
status: 400,
|
|
41
|
+
statusText: 'Bad Request',
|
|
42
|
+
headers: {},
|
|
43
|
+
config: {} as any,
|
|
44
|
+
data: {
|
|
45
|
+
statusCode: 400,
|
|
46
|
+
error: 'Bad Request',
|
|
47
|
+
message: ['email must be an email', 'password is too short'],
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const parsed = parseSDKError(axiosError);
|
|
53
|
+
expect(parsed).toBeInstanceOf(ScrymeSDKError);
|
|
54
|
+
expect(parsed.message).toBe('email must be an email; password is too short');
|
|
55
|
+
expect(parsed.status).toBe(400);
|
|
56
|
+
expect(parsed.code).toBe('ERR_BAD_REQUEST');
|
|
57
|
+
expect(parsed.data).toEqual({
|
|
58
|
+
statusCode: 400,
|
|
59
|
+
error: 'Bad Request',
|
|
60
|
+
message: ['email must be an email', 'password is too short'],
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should parse standard NestJS error response with string message', () => {
|
|
65
|
+
const axiosError = new AxiosError(
|
|
66
|
+
'Request failed with status code 404',
|
|
67
|
+
'ERR_BAD_REQUEST',
|
|
68
|
+
undefined,
|
|
69
|
+
{},
|
|
70
|
+
{
|
|
71
|
+
status: 404,
|
|
72
|
+
statusText: 'Not Found',
|
|
73
|
+
headers: {},
|
|
74
|
+
config: {} as any,
|
|
75
|
+
data: {
|
|
76
|
+
statusCode: 404,
|
|
77
|
+
error: 'Not Found',
|
|
78
|
+
message: "Workspace 'acme' not found",
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const parsed = parseSDKError(axiosError);
|
|
84
|
+
expect(parsed.message).toBe("Workspace 'acme' not found");
|
|
85
|
+
expect(parsed.status).toBe(404);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should parse error object format { error: "Unauthorized" }', () => {
|
|
89
|
+
const axiosError = new AxiosError(
|
|
90
|
+
'Request failed with status code 401',
|
|
91
|
+
'ERR_BAD_REQUEST',
|
|
92
|
+
undefined,
|
|
93
|
+
{},
|
|
94
|
+
{
|
|
95
|
+
status: 401,
|
|
96
|
+
statusText: 'Unauthorized',
|
|
97
|
+
headers: {},
|
|
98
|
+
config: {} as any,
|
|
99
|
+
data: {
|
|
100
|
+
error: 'Invalid session token',
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const parsed = parseSDKError(axiosError);
|
|
106
|
+
expect(parsed.message).toBe('Invalid session token');
|
|
107
|
+
expect(parsed.status).toBe(401);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should parse nested error object format { error: { message: "Token expired" } }', () => {
|
|
111
|
+
const axiosError = new AxiosError(
|
|
112
|
+
'Request failed with status code 401',
|
|
113
|
+
'ERR_BAD_REQUEST',
|
|
114
|
+
undefined,
|
|
115
|
+
{},
|
|
116
|
+
{
|
|
117
|
+
status: 401,
|
|
118
|
+
statusText: 'Unauthorized',
|
|
119
|
+
headers: {},
|
|
120
|
+
config: {} as any,
|
|
121
|
+
data: {
|
|
122
|
+
error: {
|
|
123
|
+
message: 'Token expired',
|
|
124
|
+
code: 'TOKEN_EXPIRED',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const parsed = parseSDKError(axiosError);
|
|
131
|
+
expect(parsed.message).toBe('Token expired');
|
|
132
|
+
expect(parsed.status).toBe(401);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should parse string response data', () => {
|
|
136
|
+
const axiosError = new AxiosError(
|
|
137
|
+
'Request failed with status code 502',
|
|
138
|
+
'ERR_BAD_GATEWAY',
|
|
139
|
+
undefined,
|
|
140
|
+
{},
|
|
141
|
+
{
|
|
142
|
+
status: 502,
|
|
143
|
+
statusText: 'Bad Gateway',
|
|
144
|
+
headers: {},
|
|
145
|
+
config: {} as any,
|
|
146
|
+
data: 'Bad Gateway Error from Upstream',
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const parsed = parseSDKError(axiosError);
|
|
151
|
+
expect(parsed.message).toBe('Bad Gateway Error from Upstream');
|
|
152
|
+
expect(parsed.status).toBe(502);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should handle network errors without response object', () => {
|
|
156
|
+
const axiosError = new AxiosError(
|
|
157
|
+
'Network Error',
|
|
158
|
+
'ERR_NETWORK',
|
|
159
|
+
undefined,
|
|
160
|
+
{},
|
|
161
|
+
undefined
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const parsed = parseSDKError(axiosError);
|
|
165
|
+
expect(parsed.message).toBe('Network Error');
|
|
166
|
+
expect(parsed.status).toBeUndefined();
|
|
167
|
+
expect(parsed.code).toBe('ERR_NETWORK');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle timeout errors without response object', () => {
|
|
171
|
+
const axiosError = new AxiosError(
|
|
172
|
+
'timeout of 10000ms exceeded',
|
|
173
|
+
'ECONNABORTED',
|
|
174
|
+
undefined,
|
|
175
|
+
{},
|
|
176
|
+
undefined
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const parsed = parseSDKError(axiosError);
|
|
180
|
+
expect(parsed.message).toBe('timeout of 10000ms exceeded');
|
|
181
|
+
expect(parsed.code).toBe('ECONNABORTED');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should handle standard non-Axios Error objects', () => {
|
|
185
|
+
const standardError = new TypeError('Failed to fetch');
|
|
186
|
+
const parsed = parseSDKError(standardError);
|
|
187
|
+
|
|
188
|
+
expect(parsed).toBeInstanceOf(ScrymeSDKError);
|
|
189
|
+
expect(parsed.message).toBe('Failed to fetch');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle primitive string errors', () => {
|
|
193
|
+
const parsed = parseSDKError('Custom error string');
|
|
194
|
+
|
|
195
|
+
expect(parsed).toBeInstanceOf(ScrymeSDKError);
|
|
196
|
+
expect(parsed.message).toBe('Custom error string');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should wrap M2M token fetch error into ScrymeSDKError', async () => {
|
|
200
|
+
const sdk = new ScrymeSDK({
|
|
201
|
+
baseURL: 'https://api.test.com',
|
|
202
|
+
clientId: 'invalid-id',
|
|
203
|
+
clientSecret: 'invalid-secret',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Mock axios post failure
|
|
207
|
+
const postSpy = vi.spyOn(axios, 'post').mockRejectedValue(
|
|
208
|
+
new AxiosError('Request failed with status code 401', 'ERR_BAD_REQUEST', undefined, {}, {
|
|
209
|
+
status: 401,
|
|
210
|
+
statusText: 'Unauthorized',
|
|
211
|
+
headers: {},
|
|
212
|
+
config: {} as any,
|
|
213
|
+
data: { message: 'Invalid M2M client credentials' },
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
await expect(sdk.getOrFetchToken()).rejects.toThrow(ScrymeSDKError);
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
await sdk.getOrFetchToken();
|
|
221
|
+
} catch (err: any) {
|
|
222
|
+
expect(err).toBeInstanceOf(ScrymeSDKError);
|
|
223
|
+
expect(err.message).toBe('Invalid M2M client credentials');
|
|
224
|
+
expect(err.status).toBe(401);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
postSpy.mockRestore();
|
|
228
|
+
});
|
|
229
|
+
});
|
package/src/custom-instance.ts
CHANGED
|
@@ -49,12 +49,115 @@ export const setGlobalToken = (token: string | null) => {
|
|
|
49
49
|
|
|
50
50
|
export const getGlobalToken = () => globalToken;
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Custom SDK Error class for meaningful, developer-friendly error messages and structured error details.
|
|
54
|
+
*/
|
|
55
|
+
export class ScrymeSDKError extends Error {
|
|
56
|
+
public readonly status?: number;
|
|
57
|
+
public readonly code?: string;
|
|
58
|
+
public readonly data?: any;
|
|
59
|
+
public readonly rawError?: any;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
message: string,
|
|
63
|
+
options?: {
|
|
64
|
+
status?: number;
|
|
65
|
+
code?: string;
|
|
66
|
+
data?: any;
|
|
67
|
+
rawError?: any;
|
|
68
|
+
}
|
|
69
|
+
) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = 'ScrymeSDKError';
|
|
72
|
+
this.status = options?.status;
|
|
73
|
+
this.code = options?.code;
|
|
74
|
+
this.data = options?.data;
|
|
75
|
+
this.rawError = options?.rawError;
|
|
76
|
+
|
|
77
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { ScrymeSDKError as ScrymeError };
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parses any error (AxiosError, Error, or unknown object) into a clean, meaningful ScrymeSDKError.
|
|
85
|
+
*/
|
|
86
|
+
export function parseSDKError(error: any): ScrymeSDKError {
|
|
87
|
+
if (error instanceof ScrymeSDKError) {
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (axios.isAxiosError(error)) {
|
|
92
|
+
const response = error.response;
|
|
93
|
+
const status = response?.status;
|
|
94
|
+
const responseData = response?.data;
|
|
95
|
+
const code = error.code || (responseData && typeof responseData === 'object' ? responseData.code : undefined);
|
|
96
|
+
|
|
97
|
+
let message: string | undefined;
|
|
98
|
+
|
|
99
|
+
if (responseData) {
|
|
100
|
+
if (typeof responseData === 'string' && responseData.trim()) {
|
|
101
|
+
message = responseData;
|
|
102
|
+
} else if (typeof responseData === 'object' && responseData !== null) {
|
|
103
|
+
if (Array.isArray(responseData.message)) {
|
|
104
|
+
message = responseData.message.filter(Boolean).join('; ');
|
|
105
|
+
} else if (typeof responseData.message === 'string' && responseData.message.trim()) {
|
|
106
|
+
message = responseData.message;
|
|
107
|
+
} else if (typeof responseData.error === 'string' && responseData.error.trim()) {
|
|
108
|
+
message = responseData.error;
|
|
109
|
+
} else if (responseData.error && typeof responseData.error === 'object' && typeof responseData.error.message === 'string') {
|
|
110
|
+
message = responseData.error.message;
|
|
111
|
+
} else if (typeof responseData.detail === 'string' && responseData.detail.trim()) {
|
|
112
|
+
message = responseData.detail;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!message) {
|
|
118
|
+
if (response?.statusText) {
|
|
119
|
+
message = `HTTP ${status}: ${response.statusText}`;
|
|
120
|
+
} else if (error.message && !error.message.startsWith('Request failed with status code')) {
|
|
121
|
+
message = error.message;
|
|
122
|
+
} else if (status) {
|
|
123
|
+
message = `Request failed with status code ${status}`;
|
|
124
|
+
} else if (error.message) {
|
|
125
|
+
message = error.message;
|
|
126
|
+
} else {
|
|
127
|
+
message = 'An unexpected error occurred during API request';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return new ScrymeSDKError(message, {
|
|
132
|
+
status,
|
|
133
|
+
code,
|
|
134
|
+
data: responseData,
|
|
135
|
+
rawError: error,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (error instanceof Error) {
|
|
140
|
+
return new ScrymeSDKError(error.message, {
|
|
141
|
+
rawError: error,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return new ScrymeSDKError(typeof error === 'string' ? error : 'An unexpected error occurred', {
|
|
146
|
+
rawError: error,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
52
150
|
export const AXIOS_INSTANCE = axios.create({
|
|
53
151
|
baseURL: getBaseURL(),
|
|
54
152
|
timeout: 10000,
|
|
55
153
|
withCredentials: true,
|
|
56
154
|
});
|
|
57
155
|
|
|
156
|
+
AXIOS_INSTANCE.interceptors.response.use(
|
|
157
|
+
response => response,
|
|
158
|
+
error => Promise.reject(parseSDKError(error))
|
|
159
|
+
);
|
|
160
|
+
|
|
58
161
|
AXIOS_INSTANCE.interceptors.request.use(config => {
|
|
59
162
|
if (!config.baseURL) {
|
|
60
163
|
config.baseURL = getBaseURL();
|
|
@@ -144,7 +247,11 @@ export const customInstance = <T>(
|
|
|
144
247
|
...options,
|
|
145
248
|
headers: mergedHeaders,
|
|
146
249
|
cancelToken: source.token,
|
|
147
|
-
})
|
|
250
|
+
})
|
|
251
|
+
.then(({ data }) => data)
|
|
252
|
+
.catch(error => {
|
|
253
|
+
throw parseSDKError(error);
|
|
254
|
+
});
|
|
148
255
|
|
|
149
256
|
// @ts-ignore
|
|
150
257
|
promise.cancel = () => {
|
|
@@ -154,5 +261,5 @@ export const customInstance = <T>(
|
|
|
154
261
|
return promise;
|
|
155
262
|
};
|
|
156
263
|
|
|
157
|
-
export type ErrorType<Error> =
|
|
264
|
+
export type ErrorType<Error> = ScrymeSDKError;
|
|
158
265
|
export type BodyType<Body> = Body;
|
package/src/sdk.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios, { AxiosRequestConfig } from 'axios';
|
|
2
|
-
import { setGlobalToken } from './custom-instance';
|
|
2
|
+
import { setGlobalToken, parseSDKError } from './custom-instance';
|
|
3
3
|
import { getSkyrmeChatAPI } from './generated/v3-server';
|
|
4
4
|
import type {
|
|
5
5
|
V3ProvisionWorkspaceDto,
|
|
@@ -724,7 +724,7 @@ export class ScrymeSDK {
|
|
|
724
724
|
}
|
|
725
725
|
} catch (error) {
|
|
726
726
|
console.error('ScrymeSDK failed to authenticate via client_credentials:', error);
|
|
727
|
-
throw error;
|
|
727
|
+
throw parseSDKError(error);
|
|
728
728
|
}
|
|
729
729
|
}
|
|
730
730
|
|
|
@@ -788,7 +788,11 @@ export class ScrymeSDK {
|
|
|
788
788
|
cleanArgs.push(config);
|
|
789
789
|
}
|
|
790
790
|
|
|
791
|
-
|
|
791
|
+
try {
|
|
792
|
+
return await (originalMethod as (...a: unknown[]) => unknown)(...cleanArgs);
|
|
793
|
+
} catch (error) {
|
|
794
|
+
throw parseSDKError(error);
|
|
795
|
+
}
|
|
792
796
|
};
|
|
793
797
|
}
|
|
794
798
|
return originalMethod;
|