@echoxyz/sonar-core 0.1.0 → 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/CHANGELOG.md +6 -0
- package/dist/index.cjs +430 -0
- package/dist/index.d.cts +182 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.js +393 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
APIError: () => APIError,
|
|
23
|
+
EntitySetupState: () => EntitySetupState,
|
|
24
|
+
EntityType: () => EntityType,
|
|
25
|
+
PrePurchaseFailureReason: () => PrePurchaseFailureReason,
|
|
26
|
+
PurchasePermitType: () => PurchasePermitType,
|
|
27
|
+
SaleEligibility: () => SaleEligibility,
|
|
28
|
+
SonarClient: () => SonarClient,
|
|
29
|
+
buildAuthorizationUrl: () => buildAuthorizationUrl,
|
|
30
|
+
createClient: () => createClient,
|
|
31
|
+
createMemoryStorage: () => createMemoryStorage,
|
|
32
|
+
createWebStorage: () => createWebStorage,
|
|
33
|
+
generatePKCEParams: () => generatePKCEParams
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/auth.ts
|
|
38
|
+
function safeDecodeExp(token) {
|
|
39
|
+
if (typeof token !== "string") {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
const parts = token.split(".");
|
|
43
|
+
if (parts.length !== 3) {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
let base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
48
|
+
const payload = JSON.parse(atob(base64));
|
|
49
|
+
const exp = payload == null ? void 0 : payload.exp;
|
|
50
|
+
if (typeof exp === "number" && isFinite(exp)) {
|
|
51
|
+
return exp * 1e3;
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
} catch {
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function decodeJwtExp(token) {
|
|
59
|
+
const ms = safeDecodeExp(token);
|
|
60
|
+
return ms ? new Date(ms) : void 0;
|
|
61
|
+
}
|
|
62
|
+
var AuthSession = class {
|
|
63
|
+
storage;
|
|
64
|
+
tokenKey;
|
|
65
|
+
onExpire;
|
|
66
|
+
listeners = /* @__PURE__ */ new Set();
|
|
67
|
+
expiryTimer;
|
|
68
|
+
constructor(opts) {
|
|
69
|
+
this.storage = opts.storage;
|
|
70
|
+
this.tokenKey = opts.tokenKey ?? "sonar:auth-token";
|
|
71
|
+
this.onExpire = opts.onExpire;
|
|
72
|
+
const token = this.getToken();
|
|
73
|
+
if (token) {
|
|
74
|
+
this.scheduleExpiry(token);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
setToken(token) {
|
|
78
|
+
this.storage.setItem(this.tokenKey, token);
|
|
79
|
+
this.clearTimer();
|
|
80
|
+
this.scheduleExpiry(token);
|
|
81
|
+
this.emit(token);
|
|
82
|
+
}
|
|
83
|
+
getToken() {
|
|
84
|
+
const v = this.storage.getItem(this.tokenKey);
|
|
85
|
+
return v ?? void 0;
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.storage.removeItem(this.tokenKey);
|
|
89
|
+
this.clearTimer();
|
|
90
|
+
this.emit(void 0);
|
|
91
|
+
if (this.onExpire) {
|
|
92
|
+
this.onExpire();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
onTokenChange(cb) {
|
|
96
|
+
this.listeners.add(cb);
|
|
97
|
+
return () => this.listeners.delete(cb);
|
|
98
|
+
}
|
|
99
|
+
emit(token) {
|
|
100
|
+
for (const cb of this.listeners) {
|
|
101
|
+
try {
|
|
102
|
+
cb(token);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
clearTimer() {
|
|
108
|
+
if (this.expiryTimer !== void 0) {
|
|
109
|
+
clearTimeout(this.expiryTimer);
|
|
110
|
+
this.expiryTimer = void 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
scheduleExpiry(token) {
|
|
114
|
+
const exp = decodeJwtExp(token);
|
|
115
|
+
if (!exp) {
|
|
116
|
+
this.clear();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const delay = Math.max(0, exp.getTime() - Date.now() - 5e3);
|
|
120
|
+
if (typeof window !== "undefined") {
|
|
121
|
+
this.expiryTimer = window.setTimeout(() => this.clear(), delay);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/storage.ts
|
|
127
|
+
function createMemoryStorage() {
|
|
128
|
+
const map = /* @__PURE__ */ new Map();
|
|
129
|
+
return {
|
|
130
|
+
getItem(key) {
|
|
131
|
+
return map.has(key) ? map.get(key) : null;
|
|
132
|
+
},
|
|
133
|
+
setItem(key, value) {
|
|
134
|
+
map.set(key, value);
|
|
135
|
+
},
|
|
136
|
+
removeItem(key) {
|
|
137
|
+
map.delete(key);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function createWebStorage() {
|
|
142
|
+
if (typeof window === "undefined") {
|
|
143
|
+
return createMemoryStorage();
|
|
144
|
+
}
|
|
145
|
+
const ls = window.localStorage;
|
|
146
|
+
return {
|
|
147
|
+
getItem(key) {
|
|
148
|
+
try {
|
|
149
|
+
return ls.getItem(key);
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
setItem(key, value) {
|
|
155
|
+
try {
|
|
156
|
+
ls.setItem(key, value);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
removeItem(key) {
|
|
161
|
+
try {
|
|
162
|
+
ls.removeItem(key);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/client.ts
|
|
170
|
+
var SonarClient = class {
|
|
171
|
+
apiURL;
|
|
172
|
+
auth;
|
|
173
|
+
fetchFn;
|
|
174
|
+
onUnauthorized;
|
|
175
|
+
constructor(args) {
|
|
176
|
+
var _a, _b, _c;
|
|
177
|
+
this.apiURL = args.apiURL;
|
|
178
|
+
this.auth = ((_a = args.opts) == null ? void 0 : _a.auth) ?? new AuthSession({ storage: createWebStorage() });
|
|
179
|
+
const fetchImpl = ((_b = args.opts) == null ? void 0 : _b.fetch) ?? globalThis.fetch;
|
|
180
|
+
if (!fetchImpl) {
|
|
181
|
+
throw new Error("A fetch implementation must be provided");
|
|
182
|
+
}
|
|
183
|
+
this.fetchFn = fetchImpl;
|
|
184
|
+
this.onUnauthorized = (_c = args.opts) == null ? void 0 : _c.onUnauthorized;
|
|
185
|
+
}
|
|
186
|
+
// Expose token management methods from the underlying AuthSession for convenience
|
|
187
|
+
setToken(token) {
|
|
188
|
+
this.auth.setToken(token);
|
|
189
|
+
}
|
|
190
|
+
getToken() {
|
|
191
|
+
return this.auth.getToken();
|
|
192
|
+
}
|
|
193
|
+
clear() {
|
|
194
|
+
this.auth.clear();
|
|
195
|
+
}
|
|
196
|
+
headers({ includeAuth = true } = {}) {
|
|
197
|
+
const headers = {
|
|
198
|
+
"Content-Type": "application/json"
|
|
199
|
+
};
|
|
200
|
+
if (includeAuth) {
|
|
201
|
+
const token = this.auth.getToken();
|
|
202
|
+
if (token) {
|
|
203
|
+
headers["authorization"] = `api:Bearer ${token}`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return headers;
|
|
207
|
+
}
|
|
208
|
+
async postJSON(path, body, { includeAuth = true } = {}) {
|
|
209
|
+
const resp = await this.fetchFn(new URL(path, this.apiURL), {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: this.headers({ includeAuth }),
|
|
212
|
+
body: JSON.stringify(body)
|
|
213
|
+
});
|
|
214
|
+
return this.parseJsonResponse(resp);
|
|
215
|
+
}
|
|
216
|
+
async parseJsonResponse(resp) {
|
|
217
|
+
const bodyText = await resp.text();
|
|
218
|
+
if (resp.status === 401 && this.onUnauthorized) {
|
|
219
|
+
try {
|
|
220
|
+
this.onUnauthorized();
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!resp.ok) {
|
|
225
|
+
let message = `Request failed with status ${resp.status}`;
|
|
226
|
+
let code;
|
|
227
|
+
let details = bodyText || void 0;
|
|
228
|
+
if (bodyText) {
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(bodyText);
|
|
231
|
+
details = parsed;
|
|
232
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
233
|
+
const parsedRecord = parsed;
|
|
234
|
+
const parsedMessage = parsedRecord.message ?? parsedRecord.Message ?? parsedRecord.error ?? parsedRecord.Error;
|
|
235
|
+
if (typeof parsedMessage === "string" && parsedMessage.trim()) {
|
|
236
|
+
message = parsedMessage;
|
|
237
|
+
}
|
|
238
|
+
const parsedCode = parsedRecord.code ?? parsedRecord.Code;
|
|
239
|
+
if (typeof parsedCode === "string" && parsedCode.trim()) {
|
|
240
|
+
code = parsedCode;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
throw new APIError(resp.status, message, code, details);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
return JSON.parse(bodyText);
|
|
250
|
+
} catch {
|
|
251
|
+
throw new APIError(
|
|
252
|
+
resp.status,
|
|
253
|
+
`Failed to parse JSON response (status ${resp.status})`,
|
|
254
|
+
void 0,
|
|
255
|
+
bodyText || void 0
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async exchangeAuthorizationCode(args) {
|
|
260
|
+
return this.postJSON(
|
|
261
|
+
"/oauth.ExchangeAuthorizationCode",
|
|
262
|
+
{
|
|
263
|
+
Code: args.code,
|
|
264
|
+
CodeVerifier: args.codeVerifier,
|
|
265
|
+
RedirectURI: args.redirectURI
|
|
266
|
+
},
|
|
267
|
+
{ includeAuth: false }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
async prePurchaseCheck(args) {
|
|
271
|
+
return this.postJSON("/externalapi.PrePurchaseCheck", {
|
|
272
|
+
SaleUUID: args.saleUUID,
|
|
273
|
+
EntityUUID: args.entityUUID,
|
|
274
|
+
EntityType: args.entityType,
|
|
275
|
+
PurchasingWalletAddress: args.walletAddress
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
async generatePurchasePermit(args) {
|
|
279
|
+
return this.postJSON("/externalapi.GenerateSalePurchasePermit", {
|
|
280
|
+
SaleUUID: args.saleUUID,
|
|
281
|
+
EntityUUID: args.entityUUID,
|
|
282
|
+
EntityType: args.entityType,
|
|
283
|
+
PurchasingWalletAddress: args.walletAddress
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
async fetchAllocation(args) {
|
|
287
|
+
return this.postJSON("/externalapi.Allocation", {
|
|
288
|
+
SaleUUID: args.saleUUID,
|
|
289
|
+
WalletAddress: args.walletAddress
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async listAvailableEntities(args) {
|
|
293
|
+
const data = await this.postJSON("/externalapi.ListAvailableEntities", {
|
|
294
|
+
SaleUUID: args.saleUUID
|
|
295
|
+
});
|
|
296
|
+
return data;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var APIError = class extends Error {
|
|
300
|
+
status;
|
|
301
|
+
code;
|
|
302
|
+
details;
|
|
303
|
+
constructor(status, message, code, details) {
|
|
304
|
+
super(message);
|
|
305
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
306
|
+
this.name = "APIError";
|
|
307
|
+
this.status = status;
|
|
308
|
+
this.code = code;
|
|
309
|
+
this.details = details;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/oauth.ts
|
|
314
|
+
var DEFAULT_FRONTEND_URL = "https://app.echo.xyz";
|
|
315
|
+
function buildAuthorizationUrl({
|
|
316
|
+
saleUUID,
|
|
317
|
+
clientUUID,
|
|
318
|
+
redirectURI,
|
|
319
|
+
state,
|
|
320
|
+
codeChallenge,
|
|
321
|
+
frontendURL = DEFAULT_FRONTEND_URL
|
|
322
|
+
}) {
|
|
323
|
+
const url = new URL("/oauth/authorize", frontendURL);
|
|
324
|
+
url.searchParams.set("client_id", clientUUID);
|
|
325
|
+
url.searchParams.set("redirect_uri", redirectURI);
|
|
326
|
+
url.searchParams.set("response_type", "code");
|
|
327
|
+
url.searchParams.set("state", state);
|
|
328
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
329
|
+
url.searchParams.set("saleUUID", saleUUID);
|
|
330
|
+
return url;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/pkce.ts
|
|
334
|
+
function base64UrlEncode(bytes) {
|
|
335
|
+
let binary = "";
|
|
336
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
337
|
+
binary += String.fromCharCode(bytes[i]);
|
|
338
|
+
}
|
|
339
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
340
|
+
}
|
|
341
|
+
function generateCodeVerifier() {
|
|
342
|
+
const bytes = new Uint8Array(32);
|
|
343
|
+
if (typeof crypto === "undefined" || typeof crypto.getRandomValues !== "function") {
|
|
344
|
+
throw new Error("crypto.getRandomValues is not available");
|
|
345
|
+
}
|
|
346
|
+
crypto.getRandomValues(bytes);
|
|
347
|
+
return base64UrlEncode(bytes);
|
|
348
|
+
}
|
|
349
|
+
async function generateCodeChallenge(codeVerifier) {
|
|
350
|
+
if (typeof crypto !== "undefined" && crypto.subtle && typeof TextEncoder !== "undefined") {
|
|
351
|
+
const data = new TextEncoder().encode(codeVerifier);
|
|
352
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
353
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
354
|
+
}
|
|
355
|
+
throw new Error("SubtleCrypto not available to compute code challenge");
|
|
356
|
+
}
|
|
357
|
+
async function generatePKCEParams() {
|
|
358
|
+
const codeVerifier = generateCodeVerifier();
|
|
359
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
360
|
+
const state = crypto.randomUUID();
|
|
361
|
+
return { codeVerifier, codeChallenge, state };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/types.ts
|
|
365
|
+
var EntityType = /* @__PURE__ */ ((EntityType2) => {
|
|
366
|
+
EntityType2["USER"] = "user";
|
|
367
|
+
EntityType2["ORGANIZATION"] = "organization";
|
|
368
|
+
return EntityType2;
|
|
369
|
+
})(EntityType || {});
|
|
370
|
+
var EntitySetupState = /* @__PURE__ */ ((EntitySetupState2) => {
|
|
371
|
+
EntitySetupState2["NOT_STARTED"] = "not-started";
|
|
372
|
+
EntitySetupState2["IN_PROGRESS"] = "in-progress";
|
|
373
|
+
EntitySetupState2["IN_REVIEW"] = "in-review";
|
|
374
|
+
EntitySetupState2["FAILURE"] = "failure";
|
|
375
|
+
EntitySetupState2["FAILURE_FINAL"] = "failure-final";
|
|
376
|
+
EntitySetupState2["COMPLETE"] = "complete";
|
|
377
|
+
return EntitySetupState2;
|
|
378
|
+
})(EntitySetupState || {});
|
|
379
|
+
var SaleEligibility = /* @__PURE__ */ ((SaleEligibility2) => {
|
|
380
|
+
SaleEligibility2["ELIGIBLE"] = "eligible";
|
|
381
|
+
SaleEligibility2["NOT_ELIGIBLE"] = "not-eligible";
|
|
382
|
+
SaleEligibility2["UNKNOWN_INCOMPLETE_SETUP"] = "unknown-incomplete-setup";
|
|
383
|
+
return SaleEligibility2;
|
|
384
|
+
})(SaleEligibility || {});
|
|
385
|
+
var PurchasePermitType = /* @__PURE__ */ ((PurchasePermitType2) => {
|
|
386
|
+
PurchasePermitType2["BASIC"] = "basic";
|
|
387
|
+
PurchasePermitType2["ALLOCATION"] = "allocation";
|
|
388
|
+
return PurchasePermitType2;
|
|
389
|
+
})(PurchasePermitType || {});
|
|
390
|
+
var PrePurchaseFailureReason = /* @__PURE__ */ ((PrePurchaseFailureReason2) => {
|
|
391
|
+
PrePurchaseFailureReason2["UNKNOWN"] = "unknown";
|
|
392
|
+
PrePurchaseFailureReason2["WALLET_RISK"] = "wallet-risk";
|
|
393
|
+
PrePurchaseFailureReason2["MAX_WALLETS_USED"] = "max-wallets-used";
|
|
394
|
+
PrePurchaseFailureReason2["REQUIRES_LIVENESS"] = "requires-liveness";
|
|
395
|
+
PrePurchaseFailureReason2["NO_RESERVED_ALLOCATION"] = "no-reserved-allocation";
|
|
396
|
+
return PrePurchaseFailureReason2;
|
|
397
|
+
})(PrePurchaseFailureReason || {});
|
|
398
|
+
|
|
399
|
+
// src/index.ts
|
|
400
|
+
var DEFAULT_API_URL = "https://api.echo.xyz";
|
|
401
|
+
function createClient(options) {
|
|
402
|
+
const { saleUUID, apiURL = DEFAULT_API_URL, auth, fetch, tokenKey, onExpire, onTokenChange } = options;
|
|
403
|
+
const authSession = auth ?? new AuthSession({
|
|
404
|
+
storage: createWebStorage(),
|
|
405
|
+
tokenKey,
|
|
406
|
+
onExpire
|
|
407
|
+
});
|
|
408
|
+
if (onTokenChange) {
|
|
409
|
+
authSession.onTokenChange(onTokenChange);
|
|
410
|
+
}
|
|
411
|
+
return new SonarClient({
|
|
412
|
+
apiURL,
|
|
413
|
+
opts: { auth: authSession, fetch, onUnauthorized: () => authSession.clear() }
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
417
|
+
0 && (module.exports = {
|
|
418
|
+
APIError,
|
|
419
|
+
EntitySetupState,
|
|
420
|
+
EntityType,
|
|
421
|
+
PrePurchaseFailureReason,
|
|
422
|
+
PurchasePermitType,
|
|
423
|
+
SaleEligibility,
|
|
424
|
+
SonarClient,
|
|
425
|
+
buildAuthorizationUrl,
|
|
426
|
+
createClient,
|
|
427
|
+
createMemoryStorage,
|
|
428
|
+
createWebStorage,
|
|
429
|
+
generatePKCEParams
|
|
430
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
interface StorageLike {
|
|
2
|
+
getItem(key: string): string | null | undefined;
|
|
3
|
+
setItem(key: string, value: string): void;
|
|
4
|
+
removeItem(key: string): void;
|
|
5
|
+
}
|
|
6
|
+
declare function createMemoryStorage(): StorageLike;
|
|
7
|
+
declare function createWebStorage(): StorageLike;
|
|
8
|
+
|
|
9
|
+
type Listener = (token?: string) => void;
|
|
10
|
+
declare class AuthSession {
|
|
11
|
+
private storage;
|
|
12
|
+
private readonly tokenKey;
|
|
13
|
+
private onExpire?;
|
|
14
|
+
private listeners;
|
|
15
|
+
private expiryTimer?;
|
|
16
|
+
constructor(opts: {
|
|
17
|
+
storage: StorageLike;
|
|
18
|
+
tokenKey?: string;
|
|
19
|
+
onExpire?: () => void;
|
|
20
|
+
});
|
|
21
|
+
setToken(token: string): void;
|
|
22
|
+
getToken(): string | undefined;
|
|
23
|
+
clear(): void;
|
|
24
|
+
onTokenChange(cb: Listener): () => void;
|
|
25
|
+
private emit;
|
|
26
|
+
private clearTimer;
|
|
27
|
+
private scheduleExpiry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Hex = `0x${string}`;
|
|
31
|
+
declare enum EntityType {
|
|
32
|
+
USER = "user",
|
|
33
|
+
ORGANIZATION = "organization"
|
|
34
|
+
}
|
|
35
|
+
declare enum EntitySetupState {
|
|
36
|
+
NOT_STARTED = "not-started",
|
|
37
|
+
IN_PROGRESS = "in-progress",
|
|
38
|
+
IN_REVIEW = "in-review",
|
|
39
|
+
FAILURE = "failure",
|
|
40
|
+
FAILURE_FINAL = "failure-final",
|
|
41
|
+
COMPLETE = "complete"
|
|
42
|
+
}
|
|
43
|
+
declare enum SaleEligibility {
|
|
44
|
+
ELIGIBLE = "eligible",
|
|
45
|
+
NOT_ELIGIBLE = "not-eligible",
|
|
46
|
+
UNKNOWN_INCOMPLETE_SETUP = "unknown-incomplete-setup"
|
|
47
|
+
}
|
|
48
|
+
type BasicPermit = {
|
|
49
|
+
EntityID: Uint8Array;
|
|
50
|
+
SaleUUID: Uint8Array;
|
|
51
|
+
Wallet: string;
|
|
52
|
+
ExpiresAt: string;
|
|
53
|
+
Payload: Uint8Array;
|
|
54
|
+
};
|
|
55
|
+
type AllocationPermit = {
|
|
56
|
+
Permit: BasicPermit;
|
|
57
|
+
ReservedAmount: string;
|
|
58
|
+
MaxAmount: string;
|
|
59
|
+
};
|
|
60
|
+
declare enum PurchasePermitType {
|
|
61
|
+
BASIC = "basic",
|
|
62
|
+
ALLOCATION = "allocation"
|
|
63
|
+
}
|
|
64
|
+
type PurchasePermit<T extends PurchasePermitType> = T extends PurchasePermitType.BASIC ? BasicPermit : T extends PurchasePermitType.ALLOCATION ? AllocationPermit : never;
|
|
65
|
+
declare enum PrePurchaseFailureReason {
|
|
66
|
+
UNKNOWN = "unknown",
|
|
67
|
+
WALLET_RISK = "wallet-risk",
|
|
68
|
+
MAX_WALLETS_USED = "max-wallets-used",
|
|
69
|
+
REQUIRES_LIVENESS = "requires-liveness",
|
|
70
|
+
NO_RESERVED_ALLOCATION = "no-reserved-allocation"
|
|
71
|
+
}
|
|
72
|
+
type EntityDetails = {
|
|
73
|
+
Label: string;
|
|
74
|
+
EntityUUID: string;
|
|
75
|
+
EntityType: EntityType;
|
|
76
|
+
EntitySetupState: string;
|
|
77
|
+
SaleEligibility: string;
|
|
78
|
+
InvestingRegion: string;
|
|
79
|
+
ObfuscatedEntityID: `0x${string}`;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
83
|
+
type PrePurchaseCheckResponse = {
|
|
84
|
+
ReadyToPurchase: boolean;
|
|
85
|
+
FailureReason: string;
|
|
86
|
+
LivenessCheckURL: string;
|
|
87
|
+
};
|
|
88
|
+
type GeneratePurchasePermitResponse = {
|
|
89
|
+
Permit: BasicPermit | AllocationPermit;
|
|
90
|
+
Signature: string;
|
|
91
|
+
};
|
|
92
|
+
type AllocationResponse = {
|
|
93
|
+
HasReservedAllocation: boolean;
|
|
94
|
+
ReservedAmountUSD: string;
|
|
95
|
+
MaxAmountUSD: string;
|
|
96
|
+
};
|
|
97
|
+
type ListAvailableEntitiesResponse = {
|
|
98
|
+
Entities: EntityDetails[];
|
|
99
|
+
};
|
|
100
|
+
type ClientOptions = {
|
|
101
|
+
auth?: AuthSession;
|
|
102
|
+
fetch?: FetchLike;
|
|
103
|
+
onUnauthorized?: () => void;
|
|
104
|
+
};
|
|
105
|
+
declare class SonarClient {
|
|
106
|
+
private readonly apiURL;
|
|
107
|
+
private readonly auth;
|
|
108
|
+
private readonly fetchFn;
|
|
109
|
+
private readonly onUnauthorized?;
|
|
110
|
+
constructor(args: {
|
|
111
|
+
apiURL: string;
|
|
112
|
+
opts?: ClientOptions;
|
|
113
|
+
});
|
|
114
|
+
setToken(token: string): void;
|
|
115
|
+
getToken(): string | undefined;
|
|
116
|
+
clear(): void;
|
|
117
|
+
private headers;
|
|
118
|
+
private postJSON;
|
|
119
|
+
private parseJsonResponse;
|
|
120
|
+
exchangeAuthorizationCode(args: {
|
|
121
|
+
code: string;
|
|
122
|
+
codeVerifier: string;
|
|
123
|
+
redirectURI: string;
|
|
124
|
+
}): Promise<{
|
|
125
|
+
token: string;
|
|
126
|
+
}>;
|
|
127
|
+
prePurchaseCheck(args: {
|
|
128
|
+
saleUUID: string;
|
|
129
|
+
entityUUID: string;
|
|
130
|
+
entityType: EntityType;
|
|
131
|
+
walletAddress: string;
|
|
132
|
+
}): Promise<PrePurchaseCheckResponse>;
|
|
133
|
+
generatePurchasePermit(args: {
|
|
134
|
+
saleUUID: string;
|
|
135
|
+
entityUUID: string;
|
|
136
|
+
entityType: EntityType;
|
|
137
|
+
walletAddress: string;
|
|
138
|
+
}): Promise<GeneratePurchasePermitResponse>;
|
|
139
|
+
fetchAllocation(args: {
|
|
140
|
+
saleUUID: string;
|
|
141
|
+
walletAddress: string;
|
|
142
|
+
}): Promise<AllocationResponse>;
|
|
143
|
+
listAvailableEntities(args: {
|
|
144
|
+
saleUUID: string;
|
|
145
|
+
}): Promise<ListAvailableEntitiesResponse>;
|
|
146
|
+
}
|
|
147
|
+
declare class APIError extends Error {
|
|
148
|
+
readonly status: number;
|
|
149
|
+
readonly code?: string;
|
|
150
|
+
readonly details?: unknown;
|
|
151
|
+
constructor(status: number, message: string, code?: string, details?: unknown);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type BuildAuthorizationUrlArgs = {
|
|
155
|
+
saleUUID: string;
|
|
156
|
+
clientUUID: string;
|
|
157
|
+
redirectURI: string;
|
|
158
|
+
state: string;
|
|
159
|
+
codeChallenge: string;
|
|
160
|
+
frontendURL?: string;
|
|
161
|
+
};
|
|
162
|
+
declare function buildAuthorizationUrl({ saleUUID, clientUUID, redirectURI, state, codeChallenge, frontendURL, }: BuildAuthorizationUrlArgs): URL;
|
|
163
|
+
|
|
164
|
+
type PKCEParams = {
|
|
165
|
+
codeVerifier: string;
|
|
166
|
+
codeChallenge: string;
|
|
167
|
+
state: string;
|
|
168
|
+
};
|
|
169
|
+
declare function generatePKCEParams(): Promise<PKCEParams>;
|
|
170
|
+
|
|
171
|
+
type CreateClientOptions = {
|
|
172
|
+
saleUUID: string;
|
|
173
|
+
apiURL?: string;
|
|
174
|
+
auth?: AuthSession;
|
|
175
|
+
fetch?: FetchLike;
|
|
176
|
+
tokenKey?: string;
|
|
177
|
+
onExpire?: () => void;
|
|
178
|
+
onTokenChange?: (token?: string) => void;
|
|
179
|
+
};
|
|
180
|
+
declare function createClient(options: CreateClientOptions): SonarClient;
|
|
181
|
+
|
|
182
|
+
export { APIError, type AllocationPermit, type AllocationResponse, type BasicPermit, type BuildAuthorizationUrlArgs, type ClientOptions, type CreateClientOptions, type EntityDetails, EntitySetupState, EntityType, type FetchLike, type GeneratePurchasePermitResponse, type Hex, type ListAvailableEntitiesResponse, type PrePurchaseCheckResponse, PrePurchaseFailureReason, type PurchasePermit, PurchasePermitType, SaleEligibility, SonarClient, type StorageLike, buildAuthorizationUrl, createClient, createMemoryStorage, createWebStorage, generatePKCEParams };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
interface StorageLike {
|
|
2
|
+
getItem(key: string): string | null | undefined;
|
|
3
|
+
setItem(key: string, value: string): void;
|
|
4
|
+
removeItem(key: string): void;
|
|
5
|
+
}
|
|
6
|
+
declare function createMemoryStorage(): StorageLike;
|
|
7
|
+
declare function createWebStorage(): StorageLike;
|
|
8
|
+
|
|
9
|
+
type Listener = (token?: string) => void;
|
|
10
|
+
declare class AuthSession {
|
|
11
|
+
private storage;
|
|
12
|
+
private readonly tokenKey;
|
|
13
|
+
private onExpire?;
|
|
14
|
+
private listeners;
|
|
15
|
+
private expiryTimer?;
|
|
16
|
+
constructor(opts: {
|
|
17
|
+
storage: StorageLike;
|
|
18
|
+
tokenKey?: string;
|
|
19
|
+
onExpire?: () => void;
|
|
20
|
+
});
|
|
21
|
+
setToken(token: string): void;
|
|
22
|
+
getToken(): string | undefined;
|
|
23
|
+
clear(): void;
|
|
24
|
+
onTokenChange(cb: Listener): () => void;
|
|
25
|
+
private emit;
|
|
26
|
+
private clearTimer;
|
|
27
|
+
private scheduleExpiry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Hex = `0x${string}`;
|
|
31
|
+
declare enum EntityType {
|
|
32
|
+
USER = "user",
|
|
33
|
+
ORGANIZATION = "organization"
|
|
34
|
+
}
|
|
35
|
+
declare enum EntitySetupState {
|
|
36
|
+
NOT_STARTED = "not-started",
|
|
37
|
+
IN_PROGRESS = "in-progress",
|
|
38
|
+
IN_REVIEW = "in-review",
|
|
39
|
+
FAILURE = "failure",
|
|
40
|
+
FAILURE_FINAL = "failure-final",
|
|
41
|
+
COMPLETE = "complete"
|
|
42
|
+
}
|
|
43
|
+
declare enum SaleEligibility {
|
|
44
|
+
ELIGIBLE = "eligible",
|
|
45
|
+
NOT_ELIGIBLE = "not-eligible",
|
|
46
|
+
UNKNOWN_INCOMPLETE_SETUP = "unknown-incomplete-setup"
|
|
47
|
+
}
|
|
48
|
+
type BasicPermit = {
|
|
49
|
+
EntityID: Uint8Array;
|
|
50
|
+
SaleUUID: Uint8Array;
|
|
51
|
+
Wallet: string;
|
|
52
|
+
ExpiresAt: string;
|
|
53
|
+
Payload: Uint8Array;
|
|
54
|
+
};
|
|
55
|
+
type AllocationPermit = {
|
|
56
|
+
Permit: BasicPermit;
|
|
57
|
+
ReservedAmount: string;
|
|
58
|
+
MaxAmount: string;
|
|
59
|
+
};
|
|
60
|
+
declare enum PurchasePermitType {
|
|
61
|
+
BASIC = "basic",
|
|
62
|
+
ALLOCATION = "allocation"
|
|
63
|
+
}
|
|
64
|
+
type PurchasePermit<T extends PurchasePermitType> = T extends PurchasePermitType.BASIC ? BasicPermit : T extends PurchasePermitType.ALLOCATION ? AllocationPermit : never;
|
|
65
|
+
declare enum PrePurchaseFailureReason {
|
|
66
|
+
UNKNOWN = "unknown",
|
|
67
|
+
WALLET_RISK = "wallet-risk",
|
|
68
|
+
MAX_WALLETS_USED = "max-wallets-used",
|
|
69
|
+
REQUIRES_LIVENESS = "requires-liveness",
|
|
70
|
+
NO_RESERVED_ALLOCATION = "no-reserved-allocation"
|
|
71
|
+
}
|
|
72
|
+
type EntityDetails = {
|
|
73
|
+
Label: string;
|
|
74
|
+
EntityUUID: string;
|
|
75
|
+
EntityType: EntityType;
|
|
76
|
+
EntitySetupState: string;
|
|
77
|
+
SaleEligibility: string;
|
|
78
|
+
InvestingRegion: string;
|
|
79
|
+
ObfuscatedEntityID: `0x${string}`;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
83
|
+
type PrePurchaseCheckResponse = {
|
|
84
|
+
ReadyToPurchase: boolean;
|
|
85
|
+
FailureReason: string;
|
|
86
|
+
LivenessCheckURL: string;
|
|
87
|
+
};
|
|
88
|
+
type GeneratePurchasePermitResponse = {
|
|
89
|
+
Permit: BasicPermit | AllocationPermit;
|
|
90
|
+
Signature: string;
|
|
91
|
+
};
|
|
92
|
+
type AllocationResponse = {
|
|
93
|
+
HasReservedAllocation: boolean;
|
|
94
|
+
ReservedAmountUSD: string;
|
|
95
|
+
MaxAmountUSD: string;
|
|
96
|
+
};
|
|
97
|
+
type ListAvailableEntitiesResponse = {
|
|
98
|
+
Entities: EntityDetails[];
|
|
99
|
+
};
|
|
100
|
+
type ClientOptions = {
|
|
101
|
+
auth?: AuthSession;
|
|
102
|
+
fetch?: FetchLike;
|
|
103
|
+
onUnauthorized?: () => void;
|
|
104
|
+
};
|
|
105
|
+
declare class SonarClient {
|
|
106
|
+
private readonly apiURL;
|
|
107
|
+
private readonly auth;
|
|
108
|
+
private readonly fetchFn;
|
|
109
|
+
private readonly onUnauthorized?;
|
|
110
|
+
constructor(args: {
|
|
111
|
+
apiURL: string;
|
|
112
|
+
opts?: ClientOptions;
|
|
113
|
+
});
|
|
114
|
+
setToken(token: string): void;
|
|
115
|
+
getToken(): string | undefined;
|
|
116
|
+
clear(): void;
|
|
117
|
+
private headers;
|
|
118
|
+
private postJSON;
|
|
119
|
+
private parseJsonResponse;
|
|
120
|
+
exchangeAuthorizationCode(args: {
|
|
121
|
+
code: string;
|
|
122
|
+
codeVerifier: string;
|
|
123
|
+
redirectURI: string;
|
|
124
|
+
}): Promise<{
|
|
125
|
+
token: string;
|
|
126
|
+
}>;
|
|
127
|
+
prePurchaseCheck(args: {
|
|
128
|
+
saleUUID: string;
|
|
129
|
+
entityUUID: string;
|
|
130
|
+
entityType: EntityType;
|
|
131
|
+
walletAddress: string;
|
|
132
|
+
}): Promise<PrePurchaseCheckResponse>;
|
|
133
|
+
generatePurchasePermit(args: {
|
|
134
|
+
saleUUID: string;
|
|
135
|
+
entityUUID: string;
|
|
136
|
+
entityType: EntityType;
|
|
137
|
+
walletAddress: string;
|
|
138
|
+
}): Promise<GeneratePurchasePermitResponse>;
|
|
139
|
+
fetchAllocation(args: {
|
|
140
|
+
saleUUID: string;
|
|
141
|
+
walletAddress: string;
|
|
142
|
+
}): Promise<AllocationResponse>;
|
|
143
|
+
listAvailableEntities(args: {
|
|
144
|
+
saleUUID: string;
|
|
145
|
+
}): Promise<ListAvailableEntitiesResponse>;
|
|
146
|
+
}
|
|
147
|
+
declare class APIError extends Error {
|
|
148
|
+
readonly status: number;
|
|
149
|
+
readonly code?: string;
|
|
150
|
+
readonly details?: unknown;
|
|
151
|
+
constructor(status: number, message: string, code?: string, details?: unknown);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type BuildAuthorizationUrlArgs = {
|
|
155
|
+
saleUUID: string;
|
|
156
|
+
clientUUID: string;
|
|
157
|
+
redirectURI: string;
|
|
158
|
+
state: string;
|
|
159
|
+
codeChallenge: string;
|
|
160
|
+
frontendURL?: string;
|
|
161
|
+
};
|
|
162
|
+
declare function buildAuthorizationUrl({ saleUUID, clientUUID, redirectURI, state, codeChallenge, frontendURL, }: BuildAuthorizationUrlArgs): URL;
|
|
163
|
+
|
|
164
|
+
type PKCEParams = {
|
|
165
|
+
codeVerifier: string;
|
|
166
|
+
codeChallenge: string;
|
|
167
|
+
state: string;
|
|
168
|
+
};
|
|
169
|
+
declare function generatePKCEParams(): Promise<PKCEParams>;
|
|
170
|
+
|
|
171
|
+
type CreateClientOptions = {
|
|
172
|
+
saleUUID: string;
|
|
173
|
+
apiURL?: string;
|
|
174
|
+
auth?: AuthSession;
|
|
175
|
+
fetch?: FetchLike;
|
|
176
|
+
tokenKey?: string;
|
|
177
|
+
onExpire?: () => void;
|
|
178
|
+
onTokenChange?: (token?: string) => void;
|
|
179
|
+
};
|
|
180
|
+
declare function createClient(options: CreateClientOptions): SonarClient;
|
|
181
|
+
|
|
182
|
+
export { APIError, type AllocationPermit, type AllocationResponse, type BasicPermit, type BuildAuthorizationUrlArgs, type ClientOptions, type CreateClientOptions, type EntityDetails, EntitySetupState, EntityType, type FetchLike, type GeneratePurchasePermitResponse, type Hex, type ListAvailableEntitiesResponse, type PrePurchaseCheckResponse, PrePurchaseFailureReason, type PurchasePermit, PurchasePermitType, SaleEligibility, SonarClient, type StorageLike, buildAuthorizationUrl, createClient, createMemoryStorage, createWebStorage, generatePKCEParams };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
// src/auth.ts
|
|
2
|
+
function safeDecodeExp(token) {
|
|
3
|
+
if (typeof token !== "string") {
|
|
4
|
+
return void 0;
|
|
5
|
+
}
|
|
6
|
+
const parts = token.split(".");
|
|
7
|
+
if (parts.length !== 3) {
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
let base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
12
|
+
const payload = JSON.parse(atob(base64));
|
|
13
|
+
const exp = payload == null ? void 0 : payload.exp;
|
|
14
|
+
if (typeof exp === "number" && isFinite(exp)) {
|
|
15
|
+
return exp * 1e3;
|
|
16
|
+
}
|
|
17
|
+
return void 0;
|
|
18
|
+
} catch {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function decodeJwtExp(token) {
|
|
23
|
+
const ms = safeDecodeExp(token);
|
|
24
|
+
return ms ? new Date(ms) : void 0;
|
|
25
|
+
}
|
|
26
|
+
var AuthSession = class {
|
|
27
|
+
storage;
|
|
28
|
+
tokenKey;
|
|
29
|
+
onExpire;
|
|
30
|
+
listeners = /* @__PURE__ */ new Set();
|
|
31
|
+
expiryTimer;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.storage = opts.storage;
|
|
34
|
+
this.tokenKey = opts.tokenKey ?? "sonar:auth-token";
|
|
35
|
+
this.onExpire = opts.onExpire;
|
|
36
|
+
const token = this.getToken();
|
|
37
|
+
if (token) {
|
|
38
|
+
this.scheduleExpiry(token);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
setToken(token) {
|
|
42
|
+
this.storage.setItem(this.tokenKey, token);
|
|
43
|
+
this.clearTimer();
|
|
44
|
+
this.scheduleExpiry(token);
|
|
45
|
+
this.emit(token);
|
|
46
|
+
}
|
|
47
|
+
getToken() {
|
|
48
|
+
const v = this.storage.getItem(this.tokenKey);
|
|
49
|
+
return v ?? void 0;
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
this.storage.removeItem(this.tokenKey);
|
|
53
|
+
this.clearTimer();
|
|
54
|
+
this.emit(void 0);
|
|
55
|
+
if (this.onExpire) {
|
|
56
|
+
this.onExpire();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
onTokenChange(cb) {
|
|
60
|
+
this.listeners.add(cb);
|
|
61
|
+
return () => this.listeners.delete(cb);
|
|
62
|
+
}
|
|
63
|
+
emit(token) {
|
|
64
|
+
for (const cb of this.listeners) {
|
|
65
|
+
try {
|
|
66
|
+
cb(token);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
clearTimer() {
|
|
72
|
+
if (this.expiryTimer !== void 0) {
|
|
73
|
+
clearTimeout(this.expiryTimer);
|
|
74
|
+
this.expiryTimer = void 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
scheduleExpiry(token) {
|
|
78
|
+
const exp = decodeJwtExp(token);
|
|
79
|
+
if (!exp) {
|
|
80
|
+
this.clear();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const delay = Math.max(0, exp.getTime() - Date.now() - 5e3);
|
|
84
|
+
if (typeof window !== "undefined") {
|
|
85
|
+
this.expiryTimer = window.setTimeout(() => this.clear(), delay);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/storage.ts
|
|
91
|
+
function createMemoryStorage() {
|
|
92
|
+
const map = /* @__PURE__ */ new Map();
|
|
93
|
+
return {
|
|
94
|
+
getItem(key) {
|
|
95
|
+
return map.has(key) ? map.get(key) : null;
|
|
96
|
+
},
|
|
97
|
+
setItem(key, value) {
|
|
98
|
+
map.set(key, value);
|
|
99
|
+
},
|
|
100
|
+
removeItem(key) {
|
|
101
|
+
map.delete(key);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function createWebStorage() {
|
|
106
|
+
if (typeof window === "undefined") {
|
|
107
|
+
return createMemoryStorage();
|
|
108
|
+
}
|
|
109
|
+
const ls = window.localStorage;
|
|
110
|
+
return {
|
|
111
|
+
getItem(key) {
|
|
112
|
+
try {
|
|
113
|
+
return ls.getItem(key);
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
setItem(key, value) {
|
|
119
|
+
try {
|
|
120
|
+
ls.setItem(key, value);
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
removeItem(key) {
|
|
125
|
+
try {
|
|
126
|
+
ls.removeItem(key);
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/client.ts
|
|
134
|
+
var SonarClient = class {
|
|
135
|
+
apiURL;
|
|
136
|
+
auth;
|
|
137
|
+
fetchFn;
|
|
138
|
+
onUnauthorized;
|
|
139
|
+
constructor(args) {
|
|
140
|
+
var _a, _b, _c;
|
|
141
|
+
this.apiURL = args.apiURL;
|
|
142
|
+
this.auth = ((_a = args.opts) == null ? void 0 : _a.auth) ?? new AuthSession({ storage: createWebStorage() });
|
|
143
|
+
const fetchImpl = ((_b = args.opts) == null ? void 0 : _b.fetch) ?? globalThis.fetch;
|
|
144
|
+
if (!fetchImpl) {
|
|
145
|
+
throw new Error("A fetch implementation must be provided");
|
|
146
|
+
}
|
|
147
|
+
this.fetchFn = fetchImpl;
|
|
148
|
+
this.onUnauthorized = (_c = args.opts) == null ? void 0 : _c.onUnauthorized;
|
|
149
|
+
}
|
|
150
|
+
// Expose token management methods from the underlying AuthSession for convenience
|
|
151
|
+
setToken(token) {
|
|
152
|
+
this.auth.setToken(token);
|
|
153
|
+
}
|
|
154
|
+
getToken() {
|
|
155
|
+
return this.auth.getToken();
|
|
156
|
+
}
|
|
157
|
+
clear() {
|
|
158
|
+
this.auth.clear();
|
|
159
|
+
}
|
|
160
|
+
headers({ includeAuth = true } = {}) {
|
|
161
|
+
const headers = {
|
|
162
|
+
"Content-Type": "application/json"
|
|
163
|
+
};
|
|
164
|
+
if (includeAuth) {
|
|
165
|
+
const token = this.auth.getToken();
|
|
166
|
+
if (token) {
|
|
167
|
+
headers["authorization"] = `api:Bearer ${token}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return headers;
|
|
171
|
+
}
|
|
172
|
+
async postJSON(path, body, { includeAuth = true } = {}) {
|
|
173
|
+
const resp = await this.fetchFn(new URL(path, this.apiURL), {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: this.headers({ includeAuth }),
|
|
176
|
+
body: JSON.stringify(body)
|
|
177
|
+
});
|
|
178
|
+
return this.parseJsonResponse(resp);
|
|
179
|
+
}
|
|
180
|
+
async parseJsonResponse(resp) {
|
|
181
|
+
const bodyText = await resp.text();
|
|
182
|
+
if (resp.status === 401 && this.onUnauthorized) {
|
|
183
|
+
try {
|
|
184
|
+
this.onUnauthorized();
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!resp.ok) {
|
|
189
|
+
let message = `Request failed with status ${resp.status}`;
|
|
190
|
+
let code;
|
|
191
|
+
let details = bodyText || void 0;
|
|
192
|
+
if (bodyText) {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(bodyText);
|
|
195
|
+
details = parsed;
|
|
196
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
197
|
+
const parsedRecord = parsed;
|
|
198
|
+
const parsedMessage = parsedRecord.message ?? parsedRecord.Message ?? parsedRecord.error ?? parsedRecord.Error;
|
|
199
|
+
if (typeof parsedMessage === "string" && parsedMessage.trim()) {
|
|
200
|
+
message = parsedMessage;
|
|
201
|
+
}
|
|
202
|
+
const parsedCode = parsedRecord.code ?? parsedRecord.Code;
|
|
203
|
+
if (typeof parsedCode === "string" && parsedCode.trim()) {
|
|
204
|
+
code = parsedCode;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw new APIError(resp.status, message, code, details);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
return JSON.parse(bodyText);
|
|
214
|
+
} catch {
|
|
215
|
+
throw new APIError(
|
|
216
|
+
resp.status,
|
|
217
|
+
`Failed to parse JSON response (status ${resp.status})`,
|
|
218
|
+
void 0,
|
|
219
|
+
bodyText || void 0
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async exchangeAuthorizationCode(args) {
|
|
224
|
+
return this.postJSON(
|
|
225
|
+
"/oauth.ExchangeAuthorizationCode",
|
|
226
|
+
{
|
|
227
|
+
Code: args.code,
|
|
228
|
+
CodeVerifier: args.codeVerifier,
|
|
229
|
+
RedirectURI: args.redirectURI
|
|
230
|
+
},
|
|
231
|
+
{ includeAuth: false }
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
async prePurchaseCheck(args) {
|
|
235
|
+
return this.postJSON("/externalapi.PrePurchaseCheck", {
|
|
236
|
+
SaleUUID: args.saleUUID,
|
|
237
|
+
EntityUUID: args.entityUUID,
|
|
238
|
+
EntityType: args.entityType,
|
|
239
|
+
PurchasingWalletAddress: args.walletAddress
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async generatePurchasePermit(args) {
|
|
243
|
+
return this.postJSON("/externalapi.GenerateSalePurchasePermit", {
|
|
244
|
+
SaleUUID: args.saleUUID,
|
|
245
|
+
EntityUUID: args.entityUUID,
|
|
246
|
+
EntityType: args.entityType,
|
|
247
|
+
PurchasingWalletAddress: args.walletAddress
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
async fetchAllocation(args) {
|
|
251
|
+
return this.postJSON("/externalapi.Allocation", {
|
|
252
|
+
SaleUUID: args.saleUUID,
|
|
253
|
+
WalletAddress: args.walletAddress
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async listAvailableEntities(args) {
|
|
257
|
+
const data = await this.postJSON("/externalapi.ListAvailableEntities", {
|
|
258
|
+
SaleUUID: args.saleUUID
|
|
259
|
+
});
|
|
260
|
+
return data;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var APIError = class extends Error {
|
|
264
|
+
status;
|
|
265
|
+
code;
|
|
266
|
+
details;
|
|
267
|
+
constructor(status, message, code, details) {
|
|
268
|
+
super(message);
|
|
269
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
270
|
+
this.name = "APIError";
|
|
271
|
+
this.status = status;
|
|
272
|
+
this.code = code;
|
|
273
|
+
this.details = details;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/oauth.ts
|
|
278
|
+
var DEFAULT_FRONTEND_URL = "https://app.echo.xyz";
|
|
279
|
+
function buildAuthorizationUrl({
|
|
280
|
+
saleUUID,
|
|
281
|
+
clientUUID,
|
|
282
|
+
redirectURI,
|
|
283
|
+
state,
|
|
284
|
+
codeChallenge,
|
|
285
|
+
frontendURL = DEFAULT_FRONTEND_URL
|
|
286
|
+
}) {
|
|
287
|
+
const url = new URL("/oauth/authorize", frontendURL);
|
|
288
|
+
url.searchParams.set("client_id", clientUUID);
|
|
289
|
+
url.searchParams.set("redirect_uri", redirectURI);
|
|
290
|
+
url.searchParams.set("response_type", "code");
|
|
291
|
+
url.searchParams.set("state", state);
|
|
292
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
293
|
+
url.searchParams.set("saleUUID", saleUUID);
|
|
294
|
+
return url;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/pkce.ts
|
|
298
|
+
function base64UrlEncode(bytes) {
|
|
299
|
+
let binary = "";
|
|
300
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
301
|
+
binary += String.fromCharCode(bytes[i]);
|
|
302
|
+
}
|
|
303
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
304
|
+
}
|
|
305
|
+
function generateCodeVerifier() {
|
|
306
|
+
const bytes = new Uint8Array(32);
|
|
307
|
+
if (typeof crypto === "undefined" || typeof crypto.getRandomValues !== "function") {
|
|
308
|
+
throw new Error("crypto.getRandomValues is not available");
|
|
309
|
+
}
|
|
310
|
+
crypto.getRandomValues(bytes);
|
|
311
|
+
return base64UrlEncode(bytes);
|
|
312
|
+
}
|
|
313
|
+
async function generateCodeChallenge(codeVerifier) {
|
|
314
|
+
if (typeof crypto !== "undefined" && crypto.subtle && typeof TextEncoder !== "undefined") {
|
|
315
|
+
const data = new TextEncoder().encode(codeVerifier);
|
|
316
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
317
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
318
|
+
}
|
|
319
|
+
throw new Error("SubtleCrypto not available to compute code challenge");
|
|
320
|
+
}
|
|
321
|
+
async function generatePKCEParams() {
|
|
322
|
+
const codeVerifier = generateCodeVerifier();
|
|
323
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
324
|
+
const state = crypto.randomUUID();
|
|
325
|
+
return { codeVerifier, codeChallenge, state };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/types.ts
|
|
329
|
+
var EntityType = /* @__PURE__ */ ((EntityType2) => {
|
|
330
|
+
EntityType2["USER"] = "user";
|
|
331
|
+
EntityType2["ORGANIZATION"] = "organization";
|
|
332
|
+
return EntityType2;
|
|
333
|
+
})(EntityType || {});
|
|
334
|
+
var EntitySetupState = /* @__PURE__ */ ((EntitySetupState2) => {
|
|
335
|
+
EntitySetupState2["NOT_STARTED"] = "not-started";
|
|
336
|
+
EntitySetupState2["IN_PROGRESS"] = "in-progress";
|
|
337
|
+
EntitySetupState2["IN_REVIEW"] = "in-review";
|
|
338
|
+
EntitySetupState2["FAILURE"] = "failure";
|
|
339
|
+
EntitySetupState2["FAILURE_FINAL"] = "failure-final";
|
|
340
|
+
EntitySetupState2["COMPLETE"] = "complete";
|
|
341
|
+
return EntitySetupState2;
|
|
342
|
+
})(EntitySetupState || {});
|
|
343
|
+
var SaleEligibility = /* @__PURE__ */ ((SaleEligibility2) => {
|
|
344
|
+
SaleEligibility2["ELIGIBLE"] = "eligible";
|
|
345
|
+
SaleEligibility2["NOT_ELIGIBLE"] = "not-eligible";
|
|
346
|
+
SaleEligibility2["UNKNOWN_INCOMPLETE_SETUP"] = "unknown-incomplete-setup";
|
|
347
|
+
return SaleEligibility2;
|
|
348
|
+
})(SaleEligibility || {});
|
|
349
|
+
var PurchasePermitType = /* @__PURE__ */ ((PurchasePermitType2) => {
|
|
350
|
+
PurchasePermitType2["BASIC"] = "basic";
|
|
351
|
+
PurchasePermitType2["ALLOCATION"] = "allocation";
|
|
352
|
+
return PurchasePermitType2;
|
|
353
|
+
})(PurchasePermitType || {});
|
|
354
|
+
var PrePurchaseFailureReason = /* @__PURE__ */ ((PrePurchaseFailureReason2) => {
|
|
355
|
+
PrePurchaseFailureReason2["UNKNOWN"] = "unknown";
|
|
356
|
+
PrePurchaseFailureReason2["WALLET_RISK"] = "wallet-risk";
|
|
357
|
+
PrePurchaseFailureReason2["MAX_WALLETS_USED"] = "max-wallets-used";
|
|
358
|
+
PrePurchaseFailureReason2["REQUIRES_LIVENESS"] = "requires-liveness";
|
|
359
|
+
PrePurchaseFailureReason2["NO_RESERVED_ALLOCATION"] = "no-reserved-allocation";
|
|
360
|
+
return PrePurchaseFailureReason2;
|
|
361
|
+
})(PrePurchaseFailureReason || {});
|
|
362
|
+
|
|
363
|
+
// src/index.ts
|
|
364
|
+
var DEFAULT_API_URL = "https://api.echo.xyz";
|
|
365
|
+
function createClient(options) {
|
|
366
|
+
const { saleUUID, apiURL = DEFAULT_API_URL, auth, fetch, tokenKey, onExpire, onTokenChange } = options;
|
|
367
|
+
const authSession = auth ?? new AuthSession({
|
|
368
|
+
storage: createWebStorage(),
|
|
369
|
+
tokenKey,
|
|
370
|
+
onExpire
|
|
371
|
+
});
|
|
372
|
+
if (onTokenChange) {
|
|
373
|
+
authSession.onTokenChange(onTokenChange);
|
|
374
|
+
}
|
|
375
|
+
return new SonarClient({
|
|
376
|
+
apiURL,
|
|
377
|
+
opts: { auth: authSession, fetch, onUnauthorized: () => authSession.clear() }
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
export {
|
|
381
|
+
APIError,
|
|
382
|
+
EntitySetupState,
|
|
383
|
+
EntityType,
|
|
384
|
+
PrePurchaseFailureReason,
|
|
385
|
+
PurchasePermitType,
|
|
386
|
+
SaleEligibility,
|
|
387
|
+
SonarClient,
|
|
388
|
+
buildAuthorizationUrl,
|
|
389
|
+
createClient,
|
|
390
|
+
createMemoryStorage,
|
|
391
|
+
createWebStorage,
|
|
392
|
+
generatePKCEParams
|
|
393
|
+
};
|