@ermis-network/ermis-chat-sdk 1.0.9 → 2.0.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/README.md +330 -0
- package/bin/init-call.js +9 -0
- package/dist/encryption/index.browser.cjs +13045 -0
- package/dist/encryption/index.browser.cjs.map +1 -0
- package/dist/encryption/index.browser.mjs +12959 -0
- package/dist/encryption/index.browser.mjs.map +1 -0
- package/dist/encryption/index.cjs +13045 -0
- package/dist/encryption/index.cjs.map +1 -0
- package/dist/encryption/index.d.mts +3 -0
- package/dist/encryption/index.d.ts +3 -0
- package/dist/encryption/index.mjs +12959 -0
- package/dist/encryption/index.mjs.map +1 -0
- package/dist/index-CcvHIY5q.d.mts +4988 -0
- package/dist/index-CcvHIY5q.d.ts +4988 -0
- package/dist/index.browser.cjs +20399 -6823
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.full-bundle.min.js +20 -18
- package/dist/index.browser.full-bundle.min.js.map +1 -1
- package/dist/index.browser.mjs +20315 -6790
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.cjs +20400 -6824
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +167 -1356
- package/dist/index.d.ts +167 -1356
- package/dist/index.mjs +20312 -6787
- package/dist/index.mjs.map +1 -1
- package/dist/wasm_worker.worker.mjs +1600 -0
- package/dist/wasm_worker.worker.mjs.map +1 -0
- package/package.json +22 -7
- package/public/e2ee-media-stream-worker.js +627 -0
- package/public/ermis_call_node_wasm_bg.wasm +0 -0
- package/public/openmls_wasm_bg.wasm +0 -0
- package/src/attachment_utils.ts +0 -148
- package/src/auth.ts +0 -352
- package/src/channel.ts +0 -1806
- package/src/channel_state.ts +0 -607
- package/src/client.ts +0 -1617
- package/src/client_state.ts +0 -55
- package/src/connection.ts +0 -587
- package/src/ermis_call_node.ts +0 -978
- package/src/errors.ts +0 -60
- package/src/events.ts +0 -46
- package/src/hevc_decoder_config.ts +0 -305
- package/src/index.ts +0 -16
- package/src/media_stream_receiver.ts +0 -525
- package/src/media_stream_sender.ts +0 -400
- package/src/shims/empty.ts +0 -1
- package/src/signal_message.ts +0 -146
- package/src/system_message.ts +0 -117
- package/src/token_manager.ts +0 -48
- package/src/types.ts +0 -581
- package/src/utils.ts +0 -534
- package/src/wasm/ermis_call_node_wasm.d.ts +0 -154
- package/src/wasm/ermis_call_node_wasm.js +0 -1498
package/src/attachment_utils.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import type { Attachment } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Normalize a file name for upload:
|
|
5
|
-
* - Remove Vietnamese diacritics
|
|
6
|
-
* - Replace đ/Đ with d/D
|
|
7
|
-
* - Replace spaces with underscores
|
|
8
|
-
* - Preserve extension
|
|
9
|
-
*/
|
|
10
|
-
export function normalizeFileName(name: string): string {
|
|
11
|
-
const dotIndex = name.lastIndexOf('.');
|
|
12
|
-
const baseName = dotIndex > 0 ? name.slice(0, dotIndex) : name;
|
|
13
|
-
const extension = dotIndex > 0 ? name.slice(dotIndex) : '';
|
|
14
|
-
|
|
15
|
-
const normalized = baseName
|
|
16
|
-
.normalize('NFD')
|
|
17
|
-
.replace(/[\u0300-\u036f]/g, '') // strip diacritics
|
|
18
|
-
.replace(/đ/g, 'd')
|
|
19
|
-
.replace(/Đ/g, 'D')
|
|
20
|
-
.replace(/\s+/g, '_');
|
|
21
|
-
|
|
22
|
-
return normalized + extension;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Check if a MIME type or file extension is HEIC/HEIF.
|
|
27
|
-
*/
|
|
28
|
-
export function isHeicFile(file: File): boolean {
|
|
29
|
-
const mime = file.type.toLowerCase();
|
|
30
|
-
const ext = file.name.toLowerCase().split('.').pop() || '';
|
|
31
|
-
return (
|
|
32
|
-
mime === 'image/heic' ||
|
|
33
|
-
mime === 'image/heif' ||
|
|
34
|
-
ext === 'heic' ||
|
|
35
|
-
ext === 'heif'
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Categorize a file by MIME type.
|
|
41
|
-
* HEIC/HEIF files are treated as 'file' (not 'image') since browsers can't render them.
|
|
42
|
-
*/
|
|
43
|
-
export function getAttachmentCategory(
|
|
44
|
-
mimeType: string,
|
|
45
|
-
fileName?: string,
|
|
46
|
-
): 'image' | 'video' | 'audio' | 'file' {
|
|
47
|
-
const mime = mimeType.toLowerCase();
|
|
48
|
-
const ext = (fileName || '').toLowerCase().split('.').pop() || '';
|
|
49
|
-
|
|
50
|
-
// HEIC/HEIF → file (not image)
|
|
51
|
-
if (mime === 'image/heic' || mime === 'image/heif' || ext === 'heic' || ext === 'heif') {
|
|
52
|
-
return 'file';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (mime.startsWith('image/')) return 'image';
|
|
56
|
-
if (mime.startsWith('video/')) return 'video';
|
|
57
|
-
if (mime.startsWith('audio/')) return 'audio';
|
|
58
|
-
return 'file';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Check if a file is a video type that supports thumbnail extraction.
|
|
63
|
-
*/
|
|
64
|
-
export function isVideoFile(file: File): boolean {
|
|
65
|
-
return (
|
|
66
|
-
file.type === 'video/mp4' ||
|
|
67
|
-
file.type === 'video/webm' ||
|
|
68
|
-
file.type === 'video/quicktime'
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Metadata for voice recording attachments.
|
|
74
|
-
*/
|
|
75
|
-
export type VoiceRecordingMeta = {
|
|
76
|
-
waveform_data: number[];
|
|
77
|
-
duration: number;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Build a normalized attachment payload from an uploaded file.
|
|
82
|
-
*
|
|
83
|
-
* @param file - Original file object
|
|
84
|
-
* @param uploadedUrl - URL returned by the upload API
|
|
85
|
-
* @param thumbUrl - Optional thumbnail URL (for video)
|
|
86
|
-
* @param voiceMeta - Optional voice recording metadata
|
|
87
|
-
*/
|
|
88
|
-
export function buildAttachmentPayload(
|
|
89
|
-
file: File,
|
|
90
|
-
uploadedUrl: string,
|
|
91
|
-
thumbUrl?: string,
|
|
92
|
-
voiceMeta?: VoiceRecordingMeta,
|
|
93
|
-
): Attachment {
|
|
94
|
-
const title = normalizeFileName(file.name);
|
|
95
|
-
const mimeType = file.type || '';
|
|
96
|
-
const category = getAttachmentCategory(mimeType, file.name);
|
|
97
|
-
|
|
98
|
-
if (voiceMeta) {
|
|
99
|
-
return {
|
|
100
|
-
type: 'voiceRecording',
|
|
101
|
-
asset_url: uploadedUrl,
|
|
102
|
-
title,
|
|
103
|
-
file_size: file.size,
|
|
104
|
-
mime_type: mimeType,
|
|
105
|
-
waveform_data: voiceMeta.waveform_data,
|
|
106
|
-
duration: voiceMeta.duration,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
switch (category) {
|
|
111
|
-
case 'image':
|
|
112
|
-
return {
|
|
113
|
-
type: 'image',
|
|
114
|
-
image_url: uploadedUrl,
|
|
115
|
-
title,
|
|
116
|
-
file_size: file.size,
|
|
117
|
-
mime_type: mimeType,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
case 'video':
|
|
121
|
-
return {
|
|
122
|
-
type: 'video',
|
|
123
|
-
asset_url: uploadedUrl,
|
|
124
|
-
title,
|
|
125
|
-
file_size: file.size,
|
|
126
|
-
mime_type: mimeType,
|
|
127
|
-
thumb_url: thumbUrl || '',
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
case 'audio':
|
|
131
|
-
return {
|
|
132
|
-
type: 'file',
|
|
133
|
-
asset_url: uploadedUrl,
|
|
134
|
-
title,
|
|
135
|
-
file_size: file.size,
|
|
136
|
-
mime_type: mimeType,
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
default:
|
|
140
|
-
return {
|
|
141
|
-
type: 'file',
|
|
142
|
-
asset_url: uploadedUrl,
|
|
143
|
-
title,
|
|
144
|
-
file_size: file.size,
|
|
145
|
-
mime_type: mimeType || '',
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
}
|
package/src/auth.ts
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
-
import { APIErrorResponse, ErmisChatOptions, ErrorFromResponse, Logger } from './types';
|
|
3
|
-
import { chatCodes, isFunction, randomId, retryInterval, sleep } from './utils';
|
|
4
|
-
import https from 'https';
|
|
5
|
-
import { isErrorResponse } from './errors';
|
|
6
|
-
|
|
7
|
-
export class ErmisAuthProvider {
|
|
8
|
-
apiKey: string;
|
|
9
|
-
baseURL?: string;
|
|
10
|
-
options?: ErmisChatOptions;
|
|
11
|
-
axiosInstance: AxiosInstance;
|
|
12
|
-
disconnected: boolean;
|
|
13
|
-
browser: boolean;
|
|
14
|
-
node: boolean;
|
|
15
|
-
logger: Logger;
|
|
16
|
-
consecutiveFailures: number;
|
|
17
|
-
userAgent?: string;
|
|
18
|
-
/** Last identifier (phone or email) used for OTP */
|
|
19
|
-
lastIdentifier?: string;
|
|
20
|
-
/**
|
|
21
|
-
* The last OTP method used ('Sms', 'Voice', or 'Email').
|
|
22
|
-
* Used to verify OTP for the correct method.
|
|
23
|
-
*/
|
|
24
|
-
lastMethod?: 'Sms' | 'Voice' | 'Email';
|
|
25
|
-
/** Wallet address used for wallet authentication */
|
|
26
|
-
address?: string;
|
|
27
|
-
|
|
28
|
-
constructor(apiKey: string, baseURL: string, options?: ErmisChatOptions) {
|
|
29
|
-
const inputOptions = options || {};
|
|
30
|
-
this.apiKey = apiKey;
|
|
31
|
-
this.baseURL = options?.userBaseURL || baseURL + '/uss/v1';
|
|
32
|
-
|
|
33
|
-
this.browser = typeof inputOptions.browser !== 'undefined' ? inputOptions.browser : typeof window !== 'undefined';
|
|
34
|
-
this.node = !this.browser;
|
|
35
|
-
this.options = {
|
|
36
|
-
// timeout: 3000,
|
|
37
|
-
withCredentials: false, // making sure cookies are not sent
|
|
38
|
-
warmUp: false,
|
|
39
|
-
recoverStateOnReconnect: true,
|
|
40
|
-
...inputOptions,
|
|
41
|
-
};
|
|
42
|
-
if (this.node && !this.options.httpsAgent) {
|
|
43
|
-
this.options.httpsAgent = new https.Agent({
|
|
44
|
-
keepAlive: true,
|
|
45
|
-
keepAliveMsecs: 3000,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
this.axiosInstance = axios.create(this.options);
|
|
49
|
-
this.logger = isFunction(inputOptions.logger) ? inputOptions.logger : () => null;
|
|
50
|
-
this.consecutiveFailures = 0;
|
|
51
|
-
this.disconnected = false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
_logApiRequest(
|
|
55
|
-
type: string,
|
|
56
|
-
url: string,
|
|
57
|
-
data: unknown,
|
|
58
|
-
config: AxiosRequestConfig & {
|
|
59
|
-
config?: AxiosRequestConfig & { maxBodyLength?: number };
|
|
60
|
-
},
|
|
61
|
-
) {
|
|
62
|
-
this.logger(
|
|
63
|
-
'info',
|
|
64
|
-
`client: ${type} - Request - ${url}- ${JSON.stringify(data)} - ${JSON.stringify(config.params)}`,
|
|
65
|
-
{
|
|
66
|
-
tags: ['api', 'api_request', 'client'],
|
|
67
|
-
url,
|
|
68
|
-
payload: data,
|
|
69
|
-
config,
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
_logApiResponse<T>(type: string, url: string, response: AxiosResponse<T>) {
|
|
75
|
-
this.logger('info', `client:${type} - Response - url: ${url} > status ${response.status}`, {
|
|
76
|
-
tags: ['api', 'api_response', 'client'],
|
|
77
|
-
url,
|
|
78
|
-
response,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
_logApiError(type: string, url: string, error: unknown, options: unknown) {
|
|
83
|
-
this.logger(
|
|
84
|
-
'error',
|
|
85
|
-
`client:${type} - Error: ${JSON.stringify(error)} - url: ${url} - options: ${JSON.stringify(options)}`,
|
|
86
|
-
{
|
|
87
|
-
tags: ['api', 'api_response', 'client'],
|
|
88
|
-
url,
|
|
89
|
-
error,
|
|
90
|
-
},
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
doAxiosRequest = async <T>(
|
|
95
|
-
type: string,
|
|
96
|
-
url: string,
|
|
97
|
-
data?: unknown,
|
|
98
|
-
options: AxiosRequestConfig & {
|
|
99
|
-
config?: AxiosRequestConfig & { maxBodyLength?: number };
|
|
100
|
-
} = {},
|
|
101
|
-
): Promise<T> => {
|
|
102
|
-
const requestConfig = this._enrichAxiosOptions(options);
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
let response: AxiosResponse<T>;
|
|
106
|
-
this._logApiRequest(type, url, data, requestConfig);
|
|
107
|
-
switch (type) {
|
|
108
|
-
case 'get':
|
|
109
|
-
response = await this.axiosInstance.get(url, requestConfig);
|
|
110
|
-
break;
|
|
111
|
-
case 'delete':
|
|
112
|
-
response = await this.axiosInstance.delete(url, requestConfig);
|
|
113
|
-
break;
|
|
114
|
-
case 'post':
|
|
115
|
-
response = await this.axiosInstance.post(url, data, requestConfig);
|
|
116
|
-
break;
|
|
117
|
-
case 'postForm':
|
|
118
|
-
response = await this.axiosInstance.postForm(url, data, requestConfig);
|
|
119
|
-
break;
|
|
120
|
-
case 'put':
|
|
121
|
-
response = await this.axiosInstance.put(url, data, requestConfig);
|
|
122
|
-
break;
|
|
123
|
-
case 'patch':
|
|
124
|
-
response = await this.axiosInstance.patch(url, data, requestConfig);
|
|
125
|
-
break;
|
|
126
|
-
case 'options':
|
|
127
|
-
response = await this.axiosInstance.options(url, requestConfig);
|
|
128
|
-
break;
|
|
129
|
-
default:
|
|
130
|
-
throw new Error('Invalid request type');
|
|
131
|
-
}
|
|
132
|
-
this._logApiResponse<T>(type, url, response);
|
|
133
|
-
this.consecutiveFailures = 0;
|
|
134
|
-
return this.handleResponse(response);
|
|
135
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
-
} catch (e: any /**TODO: generalize error types */) {
|
|
137
|
-
e.client_request_id = requestConfig.headers?.['x-client-request-id'];
|
|
138
|
-
this._logApiError(type, url, e, options);
|
|
139
|
-
this.consecutiveFailures += 1;
|
|
140
|
-
if (e.response) {
|
|
141
|
-
/** connection_fallback depends on this token expiration logic */
|
|
142
|
-
if (e.response.data.code === chatCodes.TOKEN_EXPIRED) {
|
|
143
|
-
if (this.consecutiveFailures > 1) {
|
|
144
|
-
await sleep(retryInterval(this.consecutiveFailures));
|
|
145
|
-
}
|
|
146
|
-
return await this.doAxiosRequest<T>(type, url, data, requestConfig);
|
|
147
|
-
}
|
|
148
|
-
return this.handleResponse(e.response);
|
|
149
|
-
} else {
|
|
150
|
-
throw e as AxiosError<APIErrorResponse>;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
get<T>(url: string, params?: AxiosRequestConfig['params']) {
|
|
156
|
-
return this.doAxiosRequest<T>('get', url, null, { params });
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
put<T>(url: string, data?: unknown) {
|
|
160
|
-
return this.doAxiosRequest<T>('put', url, data);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
post<T>(url: string, data?: unknown, params?: AxiosRequestConfig['params']) {
|
|
164
|
-
return this.doAxiosRequest<T>('post', url, data, { params });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
patch<T>(url: string, data?: unknown) {
|
|
168
|
-
return this.doAxiosRequest<T>('patch', url, data);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
delete<T>(url: string, params?: AxiosRequestConfig['params']) {
|
|
172
|
-
return this.doAxiosRequest<T>('delete', url, null, { params });
|
|
173
|
-
}
|
|
174
|
-
errorFromResponse(response: AxiosResponse<APIErrorResponse>): ErrorFromResponse<APIErrorResponse> {
|
|
175
|
-
let err: ErrorFromResponse<APIErrorResponse>;
|
|
176
|
-
err = new ErrorFromResponse(`ErmisChat error HTTP code: ${response.status}`);
|
|
177
|
-
if (response.data && response.data.code) {
|
|
178
|
-
err = new Error(`ErmisChat error code ${response.data.code}: ${response.data.message}`);
|
|
179
|
-
err.code = response.data.code;
|
|
180
|
-
}
|
|
181
|
-
err.response = response;
|
|
182
|
-
err.status = response.status;
|
|
183
|
-
return err;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
handleResponse<T>(response: AxiosResponse<T>) {
|
|
187
|
-
const data = response.data;
|
|
188
|
-
if (isErrorResponse(response)) {
|
|
189
|
-
throw this.errorFromResponse(response);
|
|
190
|
-
}
|
|
191
|
-
return data;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
getUserAgent() {
|
|
195
|
-
return (
|
|
196
|
-
this.userAgent || `ermis-chat-sdk-javascript-client-${this.node ? 'node' : 'browser'}-${process.env.PKG_VERSION}`
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
setUserAgent(userAgent: string) {
|
|
200
|
-
this.userAgent = userAgent;
|
|
201
|
-
}
|
|
202
|
-
_enrichAxiosOptions(
|
|
203
|
-
options: AxiosRequestConfig & { config?: AxiosRequestConfig } = {
|
|
204
|
-
params: {},
|
|
205
|
-
headers: {},
|
|
206
|
-
config: {},
|
|
207
|
-
},
|
|
208
|
-
): AxiosRequestConfig {
|
|
209
|
-
let signal: AbortSignal | null = null;
|
|
210
|
-
|
|
211
|
-
if (!options.headers?.['x-client-request-id']) {
|
|
212
|
-
options.headers = {
|
|
213
|
-
...options.headers,
|
|
214
|
-
'x-client-request-id': randomId(),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
const {
|
|
218
|
-
params: axiosRequestConfigParams,
|
|
219
|
-
headers: axiosRequestConfigHeaders,
|
|
220
|
-
...axiosRequestConfigRest
|
|
221
|
-
} = this.options?.axiosRequestConfig || {};
|
|
222
|
-
|
|
223
|
-
let user_service_params = {
|
|
224
|
-
// api_key: this.key,
|
|
225
|
-
...options.params,
|
|
226
|
-
...(axiosRequestConfigParams || {}),
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
params: user_service_params,
|
|
231
|
-
headers: {
|
|
232
|
-
'X-Stream-Client': this.getUserAgent(),
|
|
233
|
-
...options.headers,
|
|
234
|
-
...(axiosRequestConfigHeaders || {}),
|
|
235
|
-
},
|
|
236
|
-
...(signal ? { signal } : {}),
|
|
237
|
-
...options.config,
|
|
238
|
-
...(axiosRequestConfigRest || {}),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Send OTP to a phone number.
|
|
244
|
-
* @param identifier Phone number
|
|
245
|
-
* @param language Language code (e.g. 'En', 'Vi')
|
|
246
|
-
* @param method Method type (e.g. 'Sms', 'Voice')
|
|
247
|
-
*/
|
|
248
|
-
async sendOtpToPhone(identifier: string, method: 'Sms' | 'Voice'): Promise<{ success: boolean; message?: string }> {
|
|
249
|
-
this.lastIdentifier = identifier;
|
|
250
|
-
this.lastMethod = method;
|
|
251
|
-
const data = {
|
|
252
|
-
apikey: this.apiKey,
|
|
253
|
-
identifier,
|
|
254
|
-
language: 'Vi',
|
|
255
|
-
method,
|
|
256
|
-
otp_type: 'Login',
|
|
257
|
-
};
|
|
258
|
-
return this.post<{ success: boolean; message?: string }>(this.baseURL + '/auth/get_otp_new', data);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Send OTP to a email.
|
|
263
|
-
* @param identifier Email address
|
|
264
|
-
* @param language Language code (e.g. 'En', 'Vi')
|
|
265
|
-
* @param method Method type (e.g. 'Email')
|
|
266
|
-
*/
|
|
267
|
-
async sendOtpToEmail(identifier: string): Promise<{ success: boolean; message?: string }> {
|
|
268
|
-
this.lastIdentifier = identifier;
|
|
269
|
-
this.lastMethod = 'Email';
|
|
270
|
-
const data = {
|
|
271
|
-
apikey: this.apiKey,
|
|
272
|
-
identifier,
|
|
273
|
-
language: 'Vi',
|
|
274
|
-
method: 'Email',
|
|
275
|
-
otp_type: 'Login',
|
|
276
|
-
};
|
|
277
|
-
return this.post<{ success: boolean; message?: string }>(this.baseURL + '/auth/get_otp_new', data);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Verify OTP for phone or email.
|
|
282
|
-
* @param otp OTP code
|
|
283
|
-
*/
|
|
284
|
-
async verifyOtp(otp: string): Promise<{ success: boolean; message?: string }> {
|
|
285
|
-
const data = {
|
|
286
|
-
identifier: this.lastIdentifier,
|
|
287
|
-
method: this.lastMethod,
|
|
288
|
-
apikey: this.apiKey,
|
|
289
|
-
otp,
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
return this.post<{ success: boolean; message?: string }>(this.baseURL + '/auth/otp_login', data);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Login with Google.
|
|
297
|
-
* @param token Google OAuth token
|
|
298
|
-
* @param apikey API key
|
|
299
|
-
*/
|
|
300
|
-
async loginWithGoogle(token: string): Promise<{ success: boolean; message?: string }> {
|
|
301
|
-
const data = {
|
|
302
|
-
token,
|
|
303
|
-
apikey: this.apiKey,
|
|
304
|
-
};
|
|
305
|
-
return this.post<{ success: boolean; message?: string }>(this.baseURL + '/auth/google_login', data);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Get challenge for wallet login.
|
|
310
|
-
* @param address Wallet address
|
|
311
|
-
* @param apiKey API key
|
|
312
|
-
*/
|
|
313
|
-
async getWalletChallenge(address: string): Promise<any> {
|
|
314
|
-
this.address = address;
|
|
315
|
-
const response = await this.post<{ challenge: string }>(this.baseURL + '/auth/get_challenge', {
|
|
316
|
-
address,
|
|
317
|
-
apikey: this.apiKey,
|
|
318
|
-
});
|
|
319
|
-
const challenge = JSON.parse(response.challenge);
|
|
320
|
-
return challenge;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private createNonce(length: number): string {
|
|
324
|
-
let result = '';
|
|
325
|
-
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
326
|
-
const charactersLength = characters.length;
|
|
327
|
-
for (let i = 0; i < length; i++) {
|
|
328
|
-
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
329
|
-
}
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Verify wallet signature after receiving the challenge.
|
|
335
|
-
* @param address Wallet address
|
|
336
|
-
* @param signature Signature generated by the wallet
|
|
337
|
-
* @param nonce Nonce used in the challenge
|
|
338
|
-
* @returns Verification result and token if successful
|
|
339
|
-
*/
|
|
340
|
-
async verifyWalletSignature(signature: string): Promise<{ success: boolean; token?: string; message?: string }> {
|
|
341
|
-
const data = {
|
|
342
|
-
address: this.address,
|
|
343
|
-
signature,
|
|
344
|
-
nonce: this.createNonce(20),
|
|
345
|
-
apikey: this.apiKey,
|
|
346
|
-
};
|
|
347
|
-
return this.post<{ success: boolean; token?: string; message?: string }>(
|
|
348
|
-
this.baseURL + '/auth/verify_signature',
|
|
349
|
-
data,
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
}
|