@sogni-ai/expo-client 1.0.0-alpha.2
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/CHANGELOG.md +13 -0
- package/README.md +87 -0
- package/dist/cjs/Account/index.d.ts +39 -0
- package/dist/cjs/Account/index.d.ts.map +1 -0
- package/dist/cjs/Account/index.js +111 -0
- package/dist/cjs/Account/index.js.map +1 -0
- package/dist/cjs/Account/types.d.ts +25 -0
- package/dist/cjs/Account/types.d.ts.map +1 -0
- package/dist/cjs/Account/types.js +18 -0
- package/dist/cjs/Account/types.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.d.ts +51 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.js +3 -0
- package/dist/cjs/ApiClient/WebSocketClient/events.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.d.ts +34 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.js +183 -0
- package/dist/cjs/ApiClient/WebSocketClient/index.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.d.ts +24 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.js +87 -0
- package/dist/cjs/ApiClient/WebSocketClient/normalize.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.d.ts +9 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.js +3 -0
- package/dist/cjs/ApiClient/WebSocketClient/types.js.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.d.ts +61 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.d.ts.map +1 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.js +11 -0
- package/dist/cjs/ApiClient/WebSocketClient/wireTypes.js.map +1 -0
- package/dist/cjs/ApiClient/index.d.ts +57 -0
- package/dist/cjs/ApiClient/index.d.ts.map +1 -0
- package/dist/cjs/ApiClient/index.js +75 -0
- package/dist/cjs/ApiClient/index.js.map +1 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +108 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.d.ts +15 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.js +47 -0
- package/dist/cjs/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.d.ts +23 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.js +14 -0
- package/dist/cjs/lib/AuthManager/AuthManagerBase.js.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.d.ts +11 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.js +50 -0
- package/dist/cjs/lib/AuthManager/AuthStorage.js.map +1 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.d.ts +35 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.js +164 -0
- package/dist/cjs/lib/AuthManager/TokenAuthManager.js.map +1 -0
- package/dist/cjs/lib/AuthManager/index.d.ts +7 -0
- package/dist/cjs/lib/AuthManager/index.d.ts.map +1 -0
- package/dist/cjs/lib/AuthManager/index.js +13 -0
- package/dist/cjs/lib/AuthManager/index.js.map +1 -0
- package/dist/cjs/lib/Cache.d.ts +12 -0
- package/dist/cjs/lib/Cache.d.ts.map +1 -0
- package/dist/cjs/lib/Cache.js +36 -0
- package/dist/cjs/lib/Cache.js.map +1 -0
- package/dist/cjs/lib/DefaultLogger.d.ts +17 -0
- package/dist/cjs/lib/DefaultLogger.d.ts.map +1 -0
- package/dist/cjs/lib/DefaultLogger.js +36 -0
- package/dist/cjs/lib/DefaultLogger.js.map +1 -0
- package/dist/cjs/lib/RestClient.d.ts +18 -0
- package/dist/cjs/lib/RestClient.d.ts.map +1 -0
- package/dist/cjs/lib/RestClient.js +67 -0
- package/dist/cjs/lib/RestClient.js.map +1 -0
- package/dist/cjs/lib/TypedEventEmitter.d.ts +16 -0
- package/dist/cjs/lib/TypedEventEmitter.d.ts.map +1 -0
- package/dist/cjs/lib/TypedEventEmitter.js +46 -0
- package/dist/cjs/lib/TypedEventEmitter.js.map +1 -0
- package/dist/cjs/lib/base64.d.ts +3 -0
- package/dist/cjs/lib/base64.d.ts.map +1 -0
- package/dist/cjs/lib/base64.js +21 -0
- package/dist/cjs/lib/base64.js.map +1 -0
- package/dist/cjs/lib/getUUID.d.ts +12 -0
- package/dist/cjs/lib/getUUID.d.ts.map +1 -0
- package/dist/cjs/lib/getUUID.js +29 -0
- package/dist/cjs/lib/getUUID.js.map +1 -0
- package/dist/cjs/lib/utils.d.ts +10 -0
- package/dist/cjs/lib/utils.d.ts.map +1 -0
- package/dist/cjs/lib/utils.js +24 -0
- package/dist/cjs/lib/utils.js.map +1 -0
- package/dist/cjs/types/ErrorData.d.ts +7 -0
- package/dist/cjs/types/ErrorData.d.ts.map +1 -0
- package/dist/cjs/types/ErrorData.js +3 -0
- package/dist/cjs/types/ErrorData.js.map +1 -0
- package/dist/cjs/types/json.d.ts +6 -0
- package/dist/cjs/types/json.d.ts.map +1 -0
- package/dist/cjs/types/json.js +3 -0
- package/dist/cjs/types/json.js.map +1 -0
- package/dist/cjs/types/token.d.ts +2 -0
- package/dist/cjs/types/token.d.ts.map +1 -0
- package/dist/cjs/types/token.js +3 -0
- package/dist/cjs/types/token.js.map +1 -0
- package/dist/cjs/version.d.ts +2 -0
- package/dist/cjs/version.d.ts.map +1 -0
- package/dist/cjs/version.js +5 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/cjs/wallet.d.ts +6 -0
- package/dist/cjs/wallet.d.ts.map +1 -0
- package/dist/cjs/wallet.js +6 -0
- package/dist/cjs/wallet.js.map +1 -0
- package/dist/esm/Account/index.js +106 -0
- package/dist/esm/Account/index.js.map +1 -0
- package/dist/esm/Account/types.js +15 -0
- package/dist/esm/Account/types.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/events.js +2 -0
- package/dist/esm/ApiClient/WebSocketClient/events.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/index.js +178 -0
- package/dist/esm/ApiClient/WebSocketClient/index.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/normalize.js +81 -0
- package/dist/esm/ApiClient/WebSocketClient/normalize.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/types.js +2 -0
- package/dist/esm/ApiClient/WebSocketClient/types.js.map +1 -0
- package/dist/esm/ApiClient/WebSocketClient/wireTypes.js +10 -0
- package/dist/esm/ApiClient/WebSocketClient/wireTypes.js.map +1 -0
- package/dist/esm/ApiClient/index.js +68 -0
- package/dist/esm/ApiClient/index.js.map +1 -0
- package/dist/esm/index.js +66 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/AuthManager/ApiKeyAuthManager.js +42 -0
- package/dist/esm/lib/AuthManager/ApiKeyAuthManager.js.map +1 -0
- package/dist/esm/lib/AuthManager/AuthManagerBase.js +9 -0
- package/dist/esm/lib/AuthManager/AuthManagerBase.js.map +1 -0
- package/dist/esm/lib/AuthManager/AuthStorage.js +13 -0
- package/dist/esm/lib/AuthManager/AuthStorage.js.map +1 -0
- package/dist/esm/lib/AuthManager/TokenAuthManager.js +159 -0
- package/dist/esm/lib/AuthManager/TokenAuthManager.js.map +1 -0
- package/dist/esm/lib/AuthManager/index.js +5 -0
- package/dist/esm/lib/AuthManager/index.js.map +1 -0
- package/dist/esm/lib/Cache.js +33 -0
- package/dist/esm/lib/Cache.js.map +1 -0
- package/dist/esm/lib/DefaultLogger.js +32 -0
- package/dist/esm/lib/DefaultLogger.js.map +1 -0
- package/dist/esm/lib/RestClient.js +62 -0
- package/dist/esm/lib/RestClient.js.map +1 -0
- package/dist/esm/lib/TypedEventEmitter.js +44 -0
- package/dist/esm/lib/TypedEventEmitter.js.map +1 -0
- package/dist/esm/lib/base64.js +17 -0
- package/dist/esm/lib/base64.js.map +1 -0
- package/dist/esm/lib/getUUID.js +26 -0
- package/dist/esm/lib/getUUID.js.map +1 -0
- package/dist/esm/lib/utils.js +19 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/dist/esm/types/ErrorData.js +2 -0
- package/dist/esm/types/ErrorData.js.map +1 -0
- package/dist/esm/types/json.js +2 -0
- package/dist/esm/types/json.js.map +1 -0
- package/dist/esm/types/token.js +2 -0
- package/dist/esm/types/token.js.map +1 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/esm/wallet.js +2 -0
- package/dist/esm/wallet.js.map +1 -0
- package/package.json +91 -0
- package/src/Account/index.ts +118 -0
- package/src/Account/types.ts +41 -0
- package/src/ApiClient/WebSocketClient/events.ts +60 -0
- package/src/ApiClient/WebSocketClient/index.ts +224 -0
- package/src/ApiClient/WebSocketClient/normalize.ts +104 -0
- package/src/ApiClient/WebSocketClient/types.ts +10 -0
- package/src/ApiClient/WebSocketClient/wireTypes.ts +63 -0
- package/src/ApiClient/index.ts +134 -0
- package/src/index.ts +141 -0
- package/src/lib/AuthManager/ApiKeyAuthManager.ts +52 -0
- package/src/lib/AuthManager/AuthManagerBase.ts +35 -0
- package/src/lib/AuthManager/AuthStorage.ts +21 -0
- package/src/lib/AuthManager/TokenAuthManager.ts +179 -0
- package/src/lib/AuthManager/index.ts +8 -0
- package/src/lib/Cache.ts +45 -0
- package/src/lib/DefaultLogger.ts +47 -0
- package/src/lib/RestClient.ts +77 -0
- package/src/lib/TypedEventEmitter.ts +52 -0
- package/src/lib/base64.ts +17 -0
- package/src/lib/getUUID.ts +27 -0
- package/src/lib/utils.ts +21 -0
- package/src/types/ErrorData.ts +7 -0
- package/src/types/json.ts +5 -0
- package/src/types/token.ts +1 -0
- package/src/version.ts +1 -0
- package/src/wallet.ts +6 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { decodeToken, decodeRefreshToken } from '../utils';
|
|
2
|
+
import { ApiError, ApiErrorResponse } from '../../ApiClient';
|
|
3
|
+
import { Logger } from '../DefaultLogger';
|
|
4
|
+
import AuthManagerBase from './AuthManagerBase';
|
|
5
|
+
import { AuthStorage, ExpoSecureStorage } from './AuthStorage';
|
|
6
|
+
import { SocketAuthData } from '../../ApiClient/WebSocketClient/types';
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY = 'sogni.tokens';
|
|
9
|
+
|
|
10
|
+
export interface TokenAuthData {
|
|
11
|
+
token: string;
|
|
12
|
+
refreshToken: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class TokenAuthManager extends AuthManagerBase<TokenAuthData | null> {
|
|
16
|
+
private _token?: string;
|
|
17
|
+
private _tokenExpiresAt: Date = new Date(0);
|
|
18
|
+
private _refreshToken?: string;
|
|
19
|
+
private _refreshTokenExpiresAt: Date = new Date(0);
|
|
20
|
+
private _baseUrl: string;
|
|
21
|
+
private _renewTokenPromise?: Promise<string>;
|
|
22
|
+
private _storage: AuthStorage;
|
|
23
|
+
|
|
24
|
+
constructor(baseUrl: string, logger: Logger, storage?: AuthStorage) {
|
|
25
|
+
super(logger);
|
|
26
|
+
this._baseUrl = baseUrl;
|
|
27
|
+
this._storage = storage ?? new ExpoSecureStorage();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get isAuthenticated() {
|
|
31
|
+
return !!this._refreshToken && this._refreshTokenExpiresAt > new Date();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hydrate tokens from secure storage. Called by SogniClient.create() on init.
|
|
36
|
+
* Returns true if a valid session was restored, false otherwise.
|
|
37
|
+
*/
|
|
38
|
+
async restore(): Promise<boolean> {
|
|
39
|
+
let raw: string | null;
|
|
40
|
+
try {
|
|
41
|
+
raw = await this._storage.getItem(STORAGE_KEY);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
this._logger.warn('Failed to read tokens from secure storage:', e);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (!raw) return false;
|
|
47
|
+
try {
|
|
48
|
+
const tokens: TokenAuthData = JSON.parse(raw);
|
|
49
|
+
await this.authenticate(tokens);
|
|
50
|
+
return this.isAuthenticated;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this._logger.warn('Stored tokens are unreadable, clearing:', e);
|
|
53
|
+
await this._storage.removeItem(STORAGE_KEY).catch(() => undefined);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async backup(): Promise<TokenAuthData | null> {
|
|
59
|
+
if (this._token && this._refreshToken) {
|
|
60
|
+
return { token: this._token, refreshToken: this._refreshToken };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async authenticate({ refreshToken, token }: TokenAuthData) {
|
|
66
|
+
if (token) {
|
|
67
|
+
const { expiresAt } = decodeToken(token);
|
|
68
|
+
if (expiresAt > new Date()) {
|
|
69
|
+
await this._updateTokens({ token, refreshToken });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
this._refreshToken = refreshToken;
|
|
74
|
+
const { expiresAt: refreshExpiresAt } = decodeRefreshToken(refreshToken);
|
|
75
|
+
this._refreshTokenExpiresAt = refreshExpiresAt;
|
|
76
|
+
await this._renewTokenSafe();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clear() {
|
|
80
|
+
if (!this._token && !this._refreshToken) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this._refreshToken = undefined;
|
|
84
|
+
this._refreshTokenExpiresAt = new Date(0);
|
|
85
|
+
this._token = undefined;
|
|
86
|
+
this._tokenExpiresAt = new Date(0);
|
|
87
|
+
this._storage.removeItem(STORAGE_KEY).catch((e) => {
|
|
88
|
+
this._logger.warn('Failed to clear tokens from secure storage:', e);
|
|
89
|
+
});
|
|
90
|
+
this.emit('updated', false);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async authenticateRequest(option: RequestInit): Promise<RequestInit> {
|
|
94
|
+
if (this._token && this._tokenExpiresAt > new Date()) {
|
|
95
|
+
return { ...option, headers: { ...option.headers, Authorization: this._token } };
|
|
96
|
+
}
|
|
97
|
+
if (!this._refreshToken) {
|
|
98
|
+
return option;
|
|
99
|
+
}
|
|
100
|
+
const token = await this._renewTokenSafe();
|
|
101
|
+
return { ...option, headers: { ...option.headers, Authorization: token } };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async socketAuth(): Promise<SocketAuthData> {
|
|
105
|
+
const token = await this._getToken();
|
|
106
|
+
if (!token) return {};
|
|
107
|
+
return { headers: { Authorization: token } };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async _getToken(): Promise<string | undefined> {
|
|
111
|
+
if (this._token && this._tokenExpiresAt > new Date()) {
|
|
112
|
+
return this._token;
|
|
113
|
+
}
|
|
114
|
+
if (!this._refreshToken) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return this._renewTokenSafe();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private async _renewTokenSafe(): Promise<string> {
|
|
121
|
+
if (this._renewTokenPromise) {
|
|
122
|
+
return this._renewTokenPromise;
|
|
123
|
+
}
|
|
124
|
+
this._renewTokenPromise = this._renewToken();
|
|
125
|
+
this._renewTokenPromise.finally(() => {
|
|
126
|
+
this._renewTokenPromise = undefined;
|
|
127
|
+
});
|
|
128
|
+
return this._renewTokenPromise;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async _updateTokens({ token, refreshToken }: { token: string; refreshToken: string }) {
|
|
132
|
+
if (this._token === token && this._refreshToken === refreshToken) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this._token = token;
|
|
136
|
+
const { expiresAt } = decodeToken(token);
|
|
137
|
+
this._tokenExpiresAt = expiresAt;
|
|
138
|
+
this._refreshToken = refreshToken;
|
|
139
|
+
const { expiresAt: refreshExpiresAt } = decodeRefreshToken(refreshToken);
|
|
140
|
+
this._refreshTokenExpiresAt = refreshExpiresAt;
|
|
141
|
+
try {
|
|
142
|
+
await this._storage.setItem(STORAGE_KEY, JSON.stringify({ token, refreshToken }));
|
|
143
|
+
} catch (e) {
|
|
144
|
+
this._logger.warn('Failed to persist tokens to secure storage:', e);
|
|
145
|
+
}
|
|
146
|
+
this.emit('updated', true);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private async _renewToken(): Promise<string> {
|
|
150
|
+
if (this._refreshTokenExpiresAt < new Date()) {
|
|
151
|
+
throw new Error('Refresh token expired');
|
|
152
|
+
}
|
|
153
|
+
const url = new URL('/v1/account/refresh-token', this._baseUrl).toString();
|
|
154
|
+
const response = await fetch(url, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: {
|
|
157
|
+
'Content-Type': 'application/json'
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify({ refreshToken: this._refreshToken })
|
|
160
|
+
});
|
|
161
|
+
let responseData: any;
|
|
162
|
+
try {
|
|
163
|
+
responseData = await response.json();
|
|
164
|
+
} catch (e) {
|
|
165
|
+
this.clear();
|
|
166
|
+
this._logger.error('Failed to parse refresh-token response:', e);
|
|
167
|
+
throw new Error('Failed to parse response');
|
|
168
|
+
}
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
this.clear();
|
|
171
|
+
throw new ApiError(response.status, responseData as ApiErrorResponse);
|
|
172
|
+
}
|
|
173
|
+
const { token, refreshToken } = responseData.data;
|
|
174
|
+
await this._updateTokens({ token, refreshToken });
|
|
175
|
+
return this._token!;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default TokenAuthManager;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ApiKeyAuthManager from './ApiKeyAuthManager';
|
|
2
|
+
import TokenAuthManager, { TokenAuthData } from './TokenAuthManager';
|
|
3
|
+
import { AuthStorage, ExpoSecureStorage } from './AuthStorage';
|
|
4
|
+
|
|
5
|
+
export type { TokenAuthData, AuthStorage };
|
|
6
|
+
export { ApiKeyAuthManager, TokenAuthManager, ExpoSecureStorage };
|
|
7
|
+
|
|
8
|
+
export type AuthManager = ApiKeyAuthManager | TokenAuthManager;
|
package/src/lib/Cache.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
interface CacheRecord<V = any> {
|
|
2
|
+
exp: number;
|
|
3
|
+
value: V;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export default class Cache<V = any> {
|
|
7
|
+
readonly ttl: number;
|
|
8
|
+
private data: Map<string, CacheRecord<V>> = new Map();
|
|
9
|
+
private interval: ReturnType<typeof setInterval>;
|
|
10
|
+
|
|
11
|
+
constructor(defaultTTL: number) {
|
|
12
|
+
this.ttl = defaultTTL;
|
|
13
|
+
this.interval = setInterval(() => this.cleanup(), 10000);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
write(key: string, value: V, ttl?: number) {
|
|
17
|
+
this.data.set(key, {
|
|
18
|
+
exp: Date.now() + (ttl || this.ttl),
|
|
19
|
+
value
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
read(key: string): V | undefined {
|
|
24
|
+
const record = this.data.get(key);
|
|
25
|
+
return record && record.exp > Date.now() ? record.value : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
clear() {
|
|
29
|
+
this.data.clear();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dispose() {
|
|
33
|
+
clearInterval(this.interval);
|
|
34
|
+
this.data.clear();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private cleanup() {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
this.data.forEach((record, key) => {
|
|
40
|
+
if (record.exp < now) {
|
|
41
|
+
this.data.delete(key);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
2
|
+
|
|
3
|
+
export const LOG_LEVELS: Record<LogLevel, number> = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
info: 2,
|
|
7
|
+
debug: 3
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface Logger {
|
|
11
|
+
error(...args: any[]): void;
|
|
12
|
+
warn(...args: any[]): void;
|
|
13
|
+
info(...args: any[]): void;
|
|
14
|
+
debug(...args: any[]): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class DefaultLogger implements Logger {
|
|
18
|
+
private _level: number;
|
|
19
|
+
|
|
20
|
+
constructor(level: LogLevel = 'warn') {
|
|
21
|
+
this._level = LOG_LEVELS[level];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
error(...args: any[]) {
|
|
25
|
+
if (this._level >= LOG_LEVELS.error) {
|
|
26
|
+
console.error(...args);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
warn(...args: any[]) {
|
|
31
|
+
if (this._level >= LOG_LEVELS.warn) {
|
|
32
|
+
console.warn(...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
info(...args: any[]) {
|
|
37
|
+
if (this._level >= LOG_LEVELS.info) {
|
|
38
|
+
console.info(...args);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
debug(...args: any[]) {
|
|
43
|
+
if (this._level >= LOG_LEVELS.debug) {
|
|
44
|
+
console.debug(...args);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ApiError, ApiErrorResponse } from '../ApiClient';
|
|
2
|
+
import TypedEventEmitter, { EventMap } from './TypedEventEmitter';
|
|
3
|
+
import { JSONValue } from '../types/json';
|
|
4
|
+
import { Logger } from './DefaultLogger';
|
|
5
|
+
import { AuthManager } from './AuthManager';
|
|
6
|
+
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
8
|
+
|
|
9
|
+
class RestClient<E extends EventMap = never> extends TypedEventEmitter<E> {
|
|
10
|
+
readonly baseUrl: string;
|
|
11
|
+
protected _auth: AuthManager;
|
|
12
|
+
protected _logger: Logger;
|
|
13
|
+
|
|
14
|
+
constructor(baseUrl: string, auth: AuthManager, logger: Logger) {
|
|
15
|
+
super();
|
|
16
|
+
this.baseUrl = baseUrl;
|
|
17
|
+
this._auth = auth;
|
|
18
|
+
this._logger = logger;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get auth(): AuthManager {
|
|
22
|
+
return this._auth;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private formatUrl(relativeUrl: string, query: Record<string, string> = {}): string {
|
|
26
|
+
const url = new URL(relativeUrl, this.baseUrl);
|
|
27
|
+
Object.keys(query).forEach((key) => {
|
|
28
|
+
url.searchParams.append(key, query[key]!);
|
|
29
|
+
});
|
|
30
|
+
return url.toString();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async request<T = JSONValue>(url: string, options: RequestInit = {}): Promise<T> {
|
|
34
|
+
const init = await this.auth.authenticateRequest(options);
|
|
35
|
+
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(url, { ...init, signal: controller.signal });
|
|
41
|
+
return this.processResponse(response) as T;
|
|
42
|
+
} finally {
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async processResponse(response: Response): Promise<JSONValue> {
|
|
48
|
+
let responseData: any;
|
|
49
|
+
try {
|
|
50
|
+
responseData = await response.json();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this._logger.error('Failed to parse response:', e);
|
|
53
|
+
throw new Error('Failed to parse response');
|
|
54
|
+
}
|
|
55
|
+
if (response.status === 401 && this.auth.isAuthenticated) {
|
|
56
|
+
this.auth.clear();
|
|
57
|
+
}
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new ApiError(response.status, responseData as ApiErrorResponse);
|
|
60
|
+
}
|
|
61
|
+
return responseData as JSONValue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get<T = JSONValue>(path: string, query: Record<string, any> = {}): Promise<T> {
|
|
65
|
+
return this.request<T>(this.formatUrl(path, query));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
post<T = JSONValue>(path: string, body: Record<string, unknown> = {}): Promise<T> {
|
|
69
|
+
return this.request<T>(this.formatUrl(path), {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
body: JSON.stringify(body)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default RestClient;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface EventMap {
|
|
2
|
+
[event: string]: any;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type EventListener<D> = (data: D) => void;
|
|
6
|
+
|
|
7
|
+
abstract class TypedEventEmitter<E extends EventMap> {
|
|
8
|
+
protected listeners: { [K in keyof E]?: EventListener<E[K]>[] } = {};
|
|
9
|
+
|
|
10
|
+
on<T extends keyof E>(event: T, listener: EventListener<E[T]>) {
|
|
11
|
+
if (!this.listeners[event]) {
|
|
12
|
+
this.listeners[event] = [];
|
|
13
|
+
}
|
|
14
|
+
this.listeners[event]!.push(listener);
|
|
15
|
+
return () => {
|
|
16
|
+
this.off(event, listener);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
once<T extends keyof E>(event: T, listener: EventListener<E[T]>) {
|
|
21
|
+
const remove = this.on(event, (data) => {
|
|
22
|
+
remove();
|
|
23
|
+
listener(data);
|
|
24
|
+
});
|
|
25
|
+
return remove;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
off<T extends keyof E>(event: T, listener: EventListener<E[T]>) {
|
|
29
|
+
if (!this.listeners[event]) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.listeners[event] = this.listeners[event]!.filter((l) => l !== listener);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
removeAllListeners<T extends keyof E>(event?: T) {
|
|
36
|
+
if (event) {
|
|
37
|
+
delete this.listeners[event];
|
|
38
|
+
} else {
|
|
39
|
+
this.listeners = {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected emit<T extends keyof E>(event: T, data: E[T]) {
|
|
44
|
+
const listeners = this.listeners[event];
|
|
45
|
+
if (!listeners) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
listeners.forEach((listener) => listener(data));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default TypedEventEmitter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function base64Encode(str: string): string {
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
const uint8Array = encoder.encode(str);
|
|
4
|
+
const chunkSize = 8192;
|
|
5
|
+
let binaryString = '';
|
|
6
|
+
for (let i = 0; i < uint8Array.length; i += chunkSize) {
|
|
7
|
+
binaryString += String.fromCharCode(...uint8Array.subarray(i, i + chunkSize));
|
|
8
|
+
}
|
|
9
|
+
return btoa(binaryString);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function base64Decode(str: string): string {
|
|
13
|
+
const binaryString = atob(str);
|
|
14
|
+
const binaryArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
|
|
15
|
+
const decoder = new TextDecoder();
|
|
16
|
+
return decoder.decode(binaryArray);
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a v4 UUID using crypto.getRandomValues, returned in uppercase form.
|
|
3
|
+
*
|
|
4
|
+
* Mac workers in the Sogni Supernet use uppercase UUIDs; the SDK keeps that
|
|
5
|
+
* convention end-to-end so wire-level comparisons stay simple.
|
|
6
|
+
*
|
|
7
|
+
* Requires `crypto.getRandomValues` (polyfilled in Expo via
|
|
8
|
+
* `expo-standard-web-crypto/polyfill`). SogniClient.create() verifies the
|
|
9
|
+
* polyfill is present and throws a clear error otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export default function getUUID(): string {
|
|
12
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
13
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
|
|
14
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
15
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
16
|
+
return (
|
|
17
|
+
hex.slice(0, 8) +
|
|
18
|
+
'-' +
|
|
19
|
+
hex.slice(8, 12) +
|
|
20
|
+
'-' +
|
|
21
|
+
hex.slice(12, 16) +
|
|
22
|
+
'-' +
|
|
23
|
+
hex.slice(16, 20) +
|
|
24
|
+
'-' +
|
|
25
|
+
hex.slice(20)
|
|
26
|
+
).toUpperCase();
|
|
27
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jwtDecode } from 'jwt-decode';
|
|
2
|
+
|
|
3
|
+
export function decodeToken(token: string) {
|
|
4
|
+
const data = jwtDecode<{ addr: string; env: string; iat: number; exp: number }>(token);
|
|
5
|
+
return {
|
|
6
|
+
walletAddress: data.addr,
|
|
7
|
+
expiresAt: new Date(data.exp * 1000)
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function decodeRefreshToken(token: string) {
|
|
12
|
+
const data = jwtDecode<{ type: string; env: string; iat: number; exp: number }>(token);
|
|
13
|
+
return {
|
|
14
|
+
env: data.env,
|
|
15
|
+
expiresAt: new Date(data.exp * 1000)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function delay(ms: number) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type TokenType = 'sogni' | 'spark';
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LIB_VERSION = '0.1.0';
|