@echoxyz/sonar-core 0.0.3 → 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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @echoxyz/sonar-core
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 95d0235: Include dist in build output for publishing
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 41247e9: Initial implementation of core/react wrapper.
14
+
3
15
  ## 0.0.3
4
16
 
5
17
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @echoxyz/sonar-core
2
+
3
+ Headless core client for interacting with Echo’s Sonar APIs. Router-agnostic, framework-agnostic.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @echoxyz/sonar-core
9
+ ```
10
+
11
+ ## Quick start (default settings)
12
+
13
+ The default client targets Echo’s hosted API and reads the auth token from `localStorage` under the `sonar:auth-token` key.
14
+
15
+ ```ts
16
+ import { createClient, buildAuthorizationUrl, generatePKCEParams, EntityType } from "@echoxyz/sonar-core";
17
+
18
+ // Configure once at app startup
19
+ const saleUUID = "<your-sale-uuid>";
20
+ const clientUUID = "<your-oauth-client-id>";
21
+ const redirectURI = window.location.origin + "/oauth/callback";
22
+
23
+ const client = createClient({ saleUUID });
24
+
25
+ // Start OAuth login (e.g. on a button click)
26
+ export async function login() {
27
+ const { codeVerifier, codeChallenge, state } = await generatePKCEParams();
28
+
29
+ sessionStorage.setItem("sonar:oauth:state", state);
30
+ sessionStorage.setItem("sonar:oauth:verifier", codeVerifier);
31
+
32
+ const url = buildAuthorizationUrl({
33
+ saleUUID,
34
+ clientUUID,
35
+ redirectURI,
36
+ state,
37
+ codeChallenge,
38
+ });
39
+
40
+ window.location.href = url.toString();
41
+ }
42
+
43
+ // Complete OAuth on your callback route/page
44
+ export async function completeOAuthFromCallback() {
45
+ const params = new URLSearchParams(window.location.search);
46
+ const code = params.get("code");
47
+ const state = params.get("state");
48
+ if (!code || !state) {
49
+ throw new Error("Missing OAuth params");
50
+ }
51
+
52
+ const expectedState = sessionStorage.getItem("sonar:oauth:state");
53
+ const codeVerifier = sessionStorage.getItem("sonar:oauth:verifier");
54
+ if (state !== expectedState || !codeVerifier) {
55
+ throw new Error("Invalid OAuth state or missing verifier");
56
+ }
57
+
58
+ const { token } = await client.exchangeAuthorizationCode({
59
+ code,
60
+ codeVerifier,
61
+ redirectURI,
62
+ });
63
+
64
+ // Persist through the client (wired to default storage)
65
+ client.setToken(token);
66
+
67
+ // Clean up temp params
68
+ sessionStorage.removeItem("sonar:oauth:state");
69
+ sessionStorage.removeItem("sonar:oauth:verifier");
70
+ }
71
+
72
+ // Call APIs (after token is set)
73
+ export async function exampleCalls() {
74
+ // List entities available to this user for the configured sale
75
+ const { Entities } = await client.listAvailableEntities({ saleUUID });
76
+
77
+ // Run a pre-purchase check
78
+ const pre = await client.prePurchaseCheck({
79
+ saleUUID,
80
+ entityUUID: Entities[0].EntityUUID,
81
+ entityType: EntityType.USER, // or EntityType.ORGANIZATION
82
+ walletAddress: "0x1234...abcd" as `0x${string}`,
83
+ });
84
+
85
+ if (pre.ReadyToPurchase) {
86
+ // Generate a purchase permit
87
+ const permit = await client.generatePurchasePermit({
88
+ saleUUID,
89
+ entityUUID: Entities[0].EntityUUID,
90
+ entityType: EntityType.USER,
91
+ walletAddress: "0x1234...abcd" as `0x${string}`,
92
+ });
93
+ console.log(permit.Signature, permit.Permit);
94
+ }
95
+
96
+ // Fetch allocation
97
+ const alloc = await client.fetchAllocation({ saleUUID, walletAddress: "0x1234...abcd" as `0x${string}` });
98
+ console.log(alloc);
99
+ }
100
+ ```
101
+
102
+ ## Customizing the client
103
+
104
+ `createClient` accepts options so you can swap out pieces as needed:
105
+
106
+ ```ts
107
+ import { AuthSession, buildAuthorizationUrl, createClient, createMemoryStorage } from "@echoxyz/sonar-core";
108
+
109
+ const client = createClient({
110
+ saleUUID: "<sale-uuid>",
111
+ apiURL: "https://api.echo.xyz",
112
+ fetch: customFetchImplementation,
113
+ auth: new AuthSession({
114
+ storage: createMemoryStorage(),
115
+ tokenKey: "custom-token-key",
116
+ }),
117
+ });
118
+
119
+ // or let the factory create the session but tweak defaults
120
+ const clientWithCallbacks = createClient({
121
+ saleUUID: "<sale-uuid>",
122
+ onTokenChange: (token) => console.log("token updated", token),
123
+ onExpire: () => console.log("session expired"),
124
+ });
125
+
126
+ const oauthURL = buildAuthorizationUrl({
127
+ saleUUID: "<sale-uuid>",
128
+ clientUUID: "<client-id>",
129
+ redirectURI: "https://example.com/oauth/callback",
130
+ state: "opaque-state",
131
+ codeChallenge: "pkce-challenge",
132
+ frontendURL: "https://custom.echo.xyz", // optional override
133
+ });
134
+ ```
135
+
136
+ ## Notes
137
+
138
+ - Tokens are not auto-refreshed. When they expire, clear `sonar:auth-token` and re-run the OAuth flow.
139
+ - This package is headless and router-agnostic. You can integrate with any router or framework.
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
+ });
@@ -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 };
@@ -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
+ };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@echoxyz/sonar-core",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
- "module": "./dist/index.mjs",
6
+ "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.mjs",
11
- "require": "./dist/index.cjs",
12
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
13
  }
14
14
  },
15
15
  "devDependencies": {
@@ -29,6 +29,9 @@
29
29
  "README.md",
30
30
  "package.json"
31
31
  ],
32
+ "dependencies": {
33
+ "jose": "^6.1.0"
34
+ },
32
35
  "scripts": {
33
36
  "build": "tsup src/index.ts --format esm,cjs --dts",
34
37
  "dev": "tsup src/index.ts --format esm,cjs --dts --watch",