@coinlist-co/react 0.0.2 → 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.
Files changed (52) hide show
  1. package/dist/{chunk-S56KGI3U.js → chunk-6CG7JT7Q.js} +255 -45
  2. package/dist/chunk-6CG7JT7Q.js.map +1 -0
  3. package/dist/chunk-DLEKT4T4.js +118 -0
  4. package/dist/chunk-DLEKT4T4.js.map +1 -0
  5. package/dist/{chunk-W6ZBUBLA.js → chunk-GRVV7NLV.js} +33 -4
  6. package/dist/chunk-GRVV7NLV.js.map +1 -0
  7. package/dist/chunk-WFA6X7E2.js +76 -0
  8. package/dist/chunk-WFA6X7E2.js.map +1 -0
  9. package/dist/client/components/index.cjs +188 -2
  10. package/dist/client/components/index.cjs.map +1 -1
  11. package/dist/client/components/index.d.cts +41 -1
  12. package/dist/client/components/index.d.ts +41 -1
  13. package/dist/client/components/index.js +149 -4
  14. package/dist/client/components/index.js.map +1 -1
  15. package/dist/client/core/index.cjs +259 -6
  16. package/dist/client/core/index.cjs.map +1 -1
  17. package/dist/client/core/index.d.cts +4 -3
  18. package/dist/client/core/index.d.ts +4 -3
  19. package/dist/client/core/index.js +34 -4
  20. package/dist/client/hooks/index.cjs +136 -2
  21. package/dist/client/hooks/index.cjs.map +1 -1
  22. package/dist/client/hooks/index.d.cts +52 -11
  23. package/dist/client/hooks/index.d.ts +52 -11
  24. package/dist/client/hooks/index.js +13 -5
  25. package/dist/client/index.cjs +315 -6
  26. package/dist/client/index.cjs.map +1 -1
  27. package/dist/client/index.d.cts +6 -4
  28. package/dist/client/index.d.ts +6 -4
  29. package/dist/client/index.js +38 -6
  30. package/dist/client/styles.css +1 -1
  31. package/dist/coinlist-client-BiA8a2yU.d.ts +272 -0
  32. package/dist/coinlist-client-DXKFDGhP.d.cts +272 -0
  33. package/dist/newtype-yKTvFdg_.d.cts +6 -0
  34. package/dist/newtype-yKTvFdg_.d.ts +6 -0
  35. package/dist/{oauth-session-g0OPJNKn.d.ts → oauth-session-DgItaMKN.d.cts} +2 -5
  36. package/dist/{oauth-session-g0OPJNKn.d.cts → oauth-session-DnKDI5ou.d.ts} +2 -5
  37. package/dist/offer-D6p_Ijme.d.cts +30 -0
  38. package/dist/offer-DnFEFJw9.d.ts +30 -0
  39. package/dist/server/index.cjs +30 -1
  40. package/dist/server/index.cjs.map +1 -1
  41. package/dist/server/index.d.cts +3 -2
  42. package/dist/server/index.d.ts +3 -2
  43. package/dist/server/index.js +1 -1
  44. package/dist/useCompleteCoinListOAuth-6Z4DGuiH.d.cts +55 -0
  45. package/dist/useCompleteCoinListOAuth-C3G1Vsf8.d.ts +55 -0
  46. package/package.json +1 -1
  47. package/dist/chunk-CGIPLHD3.js +0 -63
  48. package/dist/chunk-CGIPLHD3.js.map +0 -1
  49. package/dist/chunk-S56KGI3U.js.map +0 -1
  50. package/dist/chunk-W6ZBUBLA.js.map +0 -1
  51. package/dist/coinlist-client-BH_Cn_7u.d.cts +0 -71
  52. package/dist/coinlist-client-fkB3J-2a.d.ts +0 -71
@@ -2,7 +2,7 @@ import {
2
2
  API_VERSION,
3
3
  Attributes,
4
4
  AuthorizationCode,
5
- COINLIST_OAUTH_URL,
5
+ COINLIST_OAUTH_PAGE_URL,
6
6
  CodeVerifier,
7
7
  HEADER_IDEMPOTENCY_KEY,
8
8
  HEADER_USER_AGENT,
@@ -11,52 +11,44 @@ import {
11
11
  PUBLIC_API_BASE_URL,
12
12
  Request,
13
13
  requestRetryMiddleware
14
- } from "./chunk-W6ZBUBLA.js";
14
+ } from "./chunk-GRVV7NLV.js";
15
15
 
16
- // src/client/core/api/middleware/attach-session-middleware.ts
17
- function attachSessionMiddleware(fetchAccessToken) {
18
- return async (request) => {
19
- if (!Attributes.isProtected(request.attributes)) {
20
- return request;
21
- }
22
- const accessToken = await fetchAccessToken(false);
23
- if (accessToken === null) {
24
- return request;
25
- }
26
- return {
27
- ...request,
28
- headers: {
29
- ...request.headers ?? {},
30
- Authorization: `Bearer ${accessToken.value}`
31
- }
32
- };
33
- };
16
+ // src/shared/api/pagination.ts
17
+ var Cursor = (value) => value;
18
+ async function fetchAllPages(fetchPage) {
19
+ const items = [];
20
+ let cursor = null;
21
+ do {
22
+ const page = await fetchPage({
23
+ after: cursor ?? void 0
24
+ });
25
+ items.push(...page.data);
26
+ cursor = page.startingAfter;
27
+ } while (cursor);
28
+ return items;
34
29
  }
35
-
36
- // src/client/core/api/middleware/session-renewal.ts
37
- function renewSessionMiddleware(fetchAccessToken) {
38
- return async ({ request, response, retry }) => {
39
- if (response.status !== 401) {
40
- return response;
30
+ var PaginatedResponse = {
31
+ fromDto: (dto, itemMapper) => ({
32
+ data: dto.data.map(itemMapper),
33
+ startingAfter: dto.starting_after ? Cursor(dto.starting_after) : null,
34
+ startingBefore: dto.starting_before ? Cursor(dto.starting_before) : null
35
+ })
36
+ };
37
+ var PaginationParams = {
38
+ toQueryParams: (params) => {
39
+ const queryParams = {};
40
+ if (params.after) {
41
+ queryParams.starting_after = params.after;
41
42
  }
42
- if (!Attributes.isProtected(request.attributes)) {
43
- return response;
43
+ if (params.before) {
44
+ queryParams.starting_before = params.before;
44
45
  }
45
- if (Attributes.wasRenewAttempted(request.attributes)) {
46
- return response;
46
+ if (params.limit) {
47
+ queryParams.limit = params.limit;
47
48
  }
48
- const newToken = await fetchAccessToken(true);
49
- if (newToken) {
50
- const nextRequest = Request.concatAttributes(
51
- request,
52
- Attributes.renewAttempted(true)
53
- );
54
- return retry(nextRequest);
55
- } else {
56
- return response;
57
- }
58
- };
59
- }
49
+ return queryParams;
50
+ }
51
+ };
60
52
 
61
53
  // src/shared/utils.ts
62
54
  async function sha256(data) {
@@ -100,6 +92,167 @@ function getUUIDv4() {
100
92
  }
101
93
  return `${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
102
94
  }
95
+ function notBlankStringOrNull(value) {
96
+ if (value?.trim()) {
97
+ return value;
98
+ } else {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ // src/shared/types/offer.ts
104
+ var OfferId = (value) => value;
105
+ var OfferSlug = (value) => value;
106
+ var Offer = {
107
+ fromDto: (dto) => ({
108
+ id: OfferId(dto.id),
109
+ slug: OfferSlug(dto.slug),
110
+ tagline: notBlankStringOrNull(dto.tagline),
111
+ bannerUrl: notBlankStringOrNull(dto.banner_url),
112
+ logoUrl: notBlankStringOrNull(dto.logo_url),
113
+ startsAt: new Date(dto.starts_at),
114
+ endsAt: new Date(dto.ends_at)
115
+ })
116
+ };
117
+
118
+ // src/shared/types/asset.ts
119
+ var AssetId = (value) => value;
120
+ var AssetCode = (value) => value;
121
+ var Asset = {
122
+ fromDto: (dto) => ({
123
+ id: AssetId(dto.id),
124
+ code: AssetCode(dto.code),
125
+ name: dto.name,
126
+ fractionalDigits: dto.fractional_digits
127
+ })
128
+ };
129
+
130
+ // src/shared/types/offer-detail.ts
131
+ var OfferOptionId = (value) => value;
132
+ var OfferOptionSlug = (value) => value;
133
+ var OfferDetail = {
134
+ fromDto: (dto) => {
135
+ if (!Array.isArray(dto.funding_assets)) {
136
+ throw new Error(`funding_assets must be an array`);
137
+ }
138
+ if (!Array.isArray(dto.options)) {
139
+ throw new Error(`options must be an array`);
140
+ }
141
+ if (!Array.isArray(dto.terms)) {
142
+ throw new Error(`terms must be an array`);
143
+ }
144
+ if (!Array.isArray(dto.links)) {
145
+ throw new Error(`links must be an array`);
146
+ }
147
+ if (!Array.isArray(dto.faqs)) {
148
+ throw new Error(`faqs must be an array`);
149
+ }
150
+ if (!Array.isArray(dto.milestones)) {
151
+ throw new Error(`milestones must be an array`);
152
+ }
153
+ return {
154
+ id: OfferId(dto.id),
155
+ slug: OfferSlug(dto.slug),
156
+ name: dto.name,
157
+ asset: Asset.fromDto(dto.asset),
158
+ fundingAssets: dto.funding_assets.map(Asset.fromDto),
159
+ about: notBlankStringOrNull(dto.about),
160
+ tagline: notBlankStringOrNull(dto.tagline),
161
+ bannerUrl: notBlankStringOrNull(dto.banner_url),
162
+ logoUrl: notBlankStringOrNull(dto.logo_url),
163
+ category: notBlankStringOrNull(dto.category),
164
+ startsAt: new Date(dto.starts_at),
165
+ endsAt: new Date(dto.ends_at),
166
+ faqs: dto.faqs.map(FaqItem.fromDto),
167
+ links: dto.links.map(Link.fromDto),
168
+ milestones: dto.milestones.map(Milestone.fromDto),
169
+ options: dto.options.map(OfferOption.fromDto),
170
+ terms: dto.terms.map(TermItem.fromDto)
171
+ };
172
+ }
173
+ };
174
+ var OfferOption = {
175
+ fromDto: (dto) => ({
176
+ id: OfferOptionId(dto.id),
177
+ slug: OfferOptionSlug(dto.slug),
178
+ bidIncrement: dto.bid_increment,
179
+ floorPriceUsd: dto.floor_price_usd,
180
+ minimumPurchaseUsd: dto.minimum_purchase_usd,
181
+ priceUsd: dto.price_usd,
182
+ saleAgreementUrl: notBlankStringOrNull(dto.sale_agreement_url),
183
+ totalTokenSupply: dto.total_token_supply
184
+ })
185
+ };
186
+ var FaqItem = {
187
+ fromDto: (dto) => ({
188
+ question: notBlankStringOrNull(dto.question),
189
+ answer: notBlankStringOrNull(dto.answer)
190
+ })
191
+ };
192
+ var Link = {
193
+ fromDto: (dto) => ({
194
+ label: notBlankStringOrNull(dto.label),
195
+ url: notBlankStringOrNull(dto.url)
196
+ })
197
+ };
198
+ var TermItem = {
199
+ fromDto: (dto) => ({
200
+ key: notBlankStringOrNull(dto.key),
201
+ value: notBlankStringOrNull(dto.value)
202
+ })
203
+ };
204
+ var Milestone = {
205
+ fromDto: (dto) => ({
206
+ name: notBlankStringOrNull(dto.name),
207
+ schedule: notBlankStringOrNull(dto.schedule),
208
+ status: dto.status
209
+ })
210
+ };
211
+
212
+ // src/client/core/api/middleware/attach-session-middleware.ts
213
+ function attachSessionMiddleware(fetchAccessToken) {
214
+ return async (request) => {
215
+ if (!Attributes.isProtected(request.attributes)) {
216
+ return request;
217
+ }
218
+ const accessToken = await fetchAccessToken(false);
219
+ if (accessToken === null) {
220
+ return request;
221
+ }
222
+ return {
223
+ ...request,
224
+ headers: {
225
+ ...request.headers ?? {},
226
+ Authorization: `Bearer ${accessToken.value}`
227
+ }
228
+ };
229
+ };
230
+ }
231
+
232
+ // src/client/core/api/middleware/session-renewal.ts
233
+ function renewSessionMiddleware(fetchAccessToken) {
234
+ return async ({ request, response, retry }) => {
235
+ if (response.status !== 401) {
236
+ return response;
237
+ }
238
+ if (!Attributes.isProtected(request.attributes)) {
239
+ return response;
240
+ }
241
+ if (Attributes.wasRenewAttempted(request.attributes)) {
242
+ return response;
243
+ }
244
+ const newToken = await fetchAccessToken(true);
245
+ if (newToken) {
246
+ const nextRequest = Request.concatAttributes(
247
+ request,
248
+ Attributes.renewAttempted(true)
249
+ );
250
+ return retry(nextRequest);
251
+ } else {
252
+ return response;
253
+ }
254
+ };
255
+ }
103
256
 
104
257
  // src/client/core/api/middleware/device-id.ts
105
258
  var DEVICE_ID_KEY = "coinlist.device_id";
@@ -181,6 +334,14 @@ var ApiClient = class {
181
334
  }
182
335
  };
183
336
 
337
+ // src/shared/types/errors.ts
338
+ var NotAuthenticatedError = class extends Error {
339
+ constructor(message = "The user is not authenticated. Go through the OAuth flow first!") {
340
+ super(message);
341
+ this.name = "NotAuthenticatedError";
342
+ }
343
+ };
344
+
184
345
  // src/client/core/coinlist-client.ts
185
346
  var OAUTH_STATE_KEY = "coinlist.oauth_state";
186
347
  var OAUTH_CODE_VERIFIER_KEY = "coinlist.oauth_code_verifier";
@@ -189,7 +350,7 @@ var CoinListClientImpl = class {
189
350
  this._config = _config;
190
351
  /** Cached access token: undefined = loading, null = no token, AccessToken = logged in */
191
352
  this._accessToken = void 0;
192
- this._api = new ApiClient(
353
+ this.api = new ApiClient(
193
354
  {
194
355
  baseUrl: _config.baseUrl || PUBLIC_API_BASE_URL,
195
356
  xApiVersion: API_VERSION
@@ -233,11 +394,16 @@ var CoinListClientImpl = class {
233
394
  code_challenge_method: "S256",
234
395
  state
235
396
  });
236
- const url = `${COINLIST_OAUTH_URL}?${params.toString()}`;
397
+ const oauthPageUrl = this._config.authorizationPageUrl ?? COINLIST_OAUTH_PAGE_URL;
398
+ const url = `${oauthPageUrl}?${params.toString()}`;
237
399
  window.location.href = url;
238
400
  }
239
401
  completeOAuth() {
240
402
  const params = new URLSearchParams(window.location.search);
403
+ const coinlistError = params.get("error");
404
+ if (coinlistError) {
405
+ return { type: "error", reason: "user_canceled" };
406
+ }
241
407
  const stateParam = params.get("state");
242
408
  if (!stateParam) {
243
409
  return { type: "error", reason: "missing_state" };
@@ -263,12 +429,56 @@ var CoinListClientImpl = class {
263
429
  logout() {
264
430
  this._accessToken = null;
265
431
  }
432
+ async fetchAllOffers() {
433
+ this.ensureAuthenticated();
434
+ return await fetchAllPages(this.fetchOffersPage.bind(this));
435
+ }
436
+ async fetchOffersPage(params) {
437
+ this.ensureAuthenticated();
438
+ const pageDto = await this.api.send({
439
+ method: "GET",
440
+ url: "/v1/offers",
441
+ queryParams: PaginationParams.toQueryParams(params),
442
+ attributes: Attributes.protected()
443
+ });
444
+ return PaginatedResponse.fromDto(pageDto, Offer.fromDto);
445
+ }
446
+ async fetchOfferDetails(id) {
447
+ this.ensureAuthenticated();
448
+ const offerDetailDto = await this.api.send({
449
+ method: "GET",
450
+ url: `/v1/offers/${id}`,
451
+ attributes: Attributes.protected()
452
+ });
453
+ return OfferDetail.fromDto(offerDetailDto);
454
+ }
455
+ ensureAuthenticated() {
456
+ if (this.getAuthState() !== "logged-in") {
457
+ throw new NotAuthenticatedError();
458
+ }
459
+ }
266
460
  };
267
461
  function createCoinListClient(config) {
268
462
  return new CoinListClientImpl(config);
269
463
  }
270
464
 
271
465
  export {
466
+ Cursor,
467
+ fetchAllPages,
468
+ PaginatedResponse,
469
+ PaginationParams,
470
+ NotAuthenticatedError,
471
+ OfferId,
472
+ OfferSlug,
473
+ Offer,
474
+ OfferOptionId,
475
+ OfferOptionSlug,
476
+ OfferDetail,
477
+ OfferOption,
478
+ FaqItem,
479
+ Link,
480
+ TermItem,
481
+ Milestone,
272
482
  createCoinListClient
273
483
  };
274
- //# sourceMappingURL=chunk-S56KGI3U.js.map
484
+ //# sourceMappingURL=chunk-6CG7JT7Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/api/pagination.ts","../src/shared/utils.ts","../src/shared/types/offer.ts","../src/shared/types/asset.ts","../src/shared/types/offer-detail.ts","../src/client/core/api/middleware/attach-session-middleware.ts","../src/client/core/api/middleware/session-renewal.ts","../src/client/core/api/middleware/device-id.ts","../src/client/core/api/middleware/user-agent.ts","../src/shared/api/middleware/idempotency-key.ts","../src/client/core/api/api-client.ts","../src/shared/types/errors.ts","../src/client/core/coinlist-client.ts"],"sourcesContent":["import type { QueryParamValue } from '@/shared/api/http';\nimport type { Newtype } from '@/shared/types/newtype';\n\nexport type Cursor = Newtype<string, 'Cursor'>;\nexport const Cursor = (value: string) => value as Cursor;\n\n/**\n * Cursor-based pagination input used when requesting paginated API resources.\n * Set `after` or `before` to navigate relative to a known cursor, and `limit`\n * to control the maximum number of returned items.\n */\nexport interface PaginationParams {\n before?: Cursor;\n after?: Cursor;\n limit?: number;\n}\n\nexport interface PaginatedResponseDto<T> {\n data: T[];\n starting_after?: string;\n starting_before?: string;\n}\n\nexport interface PaginatedResponse<T> {\n data: T[];\n startingAfter: Cursor | null;\n startingBefore: Cursor | null;\n}\n\nexport async function fetchAllPages<A>(\n fetchPage: (params: PaginationParams) => Promise<PaginatedResponse<A>>\n): Promise<A[]> {\n const items: A[] = [];\n let cursor: Cursor | null = null;\n\n do {\n const page = await fetchPage({\n after: cursor ?? undefined,\n });\n items.push(...page.data);\n cursor = page.startingAfter;\n } while (cursor);\n\n return items;\n}\n\nexport const PaginatedResponse = {\n fromDto: <A, B>(\n dto: PaginatedResponseDto<A>,\n itemMapper: (item: A) => B\n ): PaginatedResponse<B> => ({\n data: dto.data.map(itemMapper),\n startingAfter: dto.starting_after ? Cursor(dto.starting_after) : null,\n startingBefore: dto.starting_before ? Cursor(dto.starting_before) : null,\n }),\n};\n\nexport const PaginationParams = {\n toQueryParams: (\n params: PaginationParams\n ): Record<string, QueryParamValue> => {\n const queryParams: Record<string, QueryParamValue> = {};\n if (params.after) {\n queryParams.starting_after = params.after;\n }\n if (params.before) {\n queryParams.starting_before = params.before;\n }\n if (params.limit) {\n queryParams.limit = params.limit;\n }\n return queryParams;\n },\n};\n","/**\n * Computes SHA-256 digest of the input (UTF-8 string or raw bytes).\n * Uses the Web Crypto API.\n */\nexport async function sha256(data: string | Uint8Array): Promise<ArrayBuffer> {\n const bytes =\n typeof data === 'string' ? new TextEncoder().encode(data) : data;\n const buffer = bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n ) as ArrayBuffer;\n return crypto.subtle.digest('SHA-256', buffer);\n}\n\n/**\n * Converts an ArrayBuffer to Base64-URL encoding (RFC 4648).\n * @param padding - If false, omits trailing '=' padding (e.g. for PKCE).\n */\nexport function arrayBufferToBase64Url(\n buffer: ArrayBuffer,\n padding: boolean = true\n): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n let base64 = btoa(binary);\n base64 = base64.replace(/\\+/g, '-').replace(/\\//g, '_');\n if (!padding) {\n base64 = base64.replace(/=+$/, '');\n }\n return base64;\n}\n\n/**\n * Securely generates random bytes and returns them as Base64-URL (no padding).\n * Suitable for PKCE code_verifier and OAuth state.\n * @param byteLength - Number of random bytes (e.g. 32 for PKCE).\n */\nexport function generateSecureRandomBase64Url(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n crypto.getRandomValues(bytes);\n const buffer = bytes.buffer;\n return arrayBufferToBase64Url(buffer, false);\n}\n\n/**\n * Generate a UUID v4 using the standard Web Crypto API (crypto.randomUUID).\n * Falls back to RFC 4122–compliant generation via crypto.getRandomValues in old browsers.\n */\nexport function getUUIDv4(): string {\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.randomUUID === 'function'\n ) {\n return crypto.randomUUID();\n }\n\n if (\n typeof crypto !== 'undefined' &&\n typeof crypto.getRandomValues === 'function'\n ) {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0'));\n return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;\n }\n\n return `${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;\n}\n\nexport function notBlankStringOrNull(value?: string | null): string | null {\n if (value?.trim()) {\n return value;\n } else {\n return null;\n }\n}\n","import type { OfferDto } from '@/shared/types/dto/offer';\nimport type { Newtype } from '@/shared/types/newtype';\nimport { notBlankStringOrNull } from '@/shared/utils';\n\nexport type OfferId = Newtype<string, 'OfferId'>;\nexport const OfferId = (value: string) => value as OfferId;\n\nexport type OfferSlug = Newtype<string, 'OfferSlug'>;\nexport const OfferSlug = (value: string) => value as OfferSlug;\n\nexport type Offer = {\n id: OfferId;\n slug: OfferSlug;\n tagline: string | null;\n bannerUrl: string | null;\n logoUrl: string | null;\n startsAt: Date;\n endsAt: Date;\n};\n\nexport const Offer = {\n fromDto: (dto: OfferDto): Offer => ({\n id: OfferId(dto.id),\n slug: OfferSlug(dto.slug),\n tagline: notBlankStringOrNull(dto.tagline),\n bannerUrl: notBlankStringOrNull(dto.banner_url),\n logoUrl: notBlankStringOrNull(dto.logo_url),\n startsAt: new Date(dto.starts_at),\n endsAt: new Date(dto.ends_at),\n }),\n};\n","import type { AssetDto } from '@/shared/types/dto/asset';\nimport type { Newtype } from '@/shared/types/newtype';\n\nexport type AssetId = Newtype<string, 'AssetId'>;\nexport const AssetId = (value: string) => value as AssetId;\n\nexport type AssetCode = Newtype<string, 'AssetCode'>;\nexport const AssetCode = (value: string) => value as AssetCode;\n\nexport type Asset = {\n id: AssetId;\n code: AssetCode;\n name: string;\n fractionalDigits: number;\n};\n\nexport const Asset = {\n fromDto: (dto: AssetDto): Asset => ({\n id: AssetId(dto.id),\n code: AssetCode(dto.code),\n name: dto.name,\n fractionalDigits: dto.fractional_digits,\n }),\n};\n","import { Asset } from '@/shared/types/asset';\nimport type {\n OfferDetailDto,\n OfferDetailFaqDto,\n OfferDetailLinkDto,\n OfferDetailMilestoneDto,\n OfferDetailOptionDto,\n OfferDetailTermDto,\n} from '@/shared/types/dto/offer-detail';\nimport type { Newtype } from '@/shared/types/newtype';\nimport { OfferId, OfferSlug } from '@/shared/types/offer';\nimport { notBlankStringOrNull } from '@/shared/utils';\n\nexport type OfferDetail = {\n id: OfferId;\n slug: OfferSlug;\n name: string;\n\n asset: Asset;\n fundingAssets: Asset[];\n\n about: string | null;\n tagline: string | null;\n bannerUrl: string | null;\n logoUrl: string | null;\n category: string | null;\n\n startsAt: Date;\n endsAt: Date;\n\n faqs: FaqItem[];\n links: Link[];\n milestones: Milestone[];\n options: OfferOption[];\n terms: TermItem[];\n};\n\nexport type OfferOptionId = Newtype<string, 'OfferOptionId'>;\nexport const OfferOptionId = (value: string) => value as OfferOptionId;\n\nexport type OfferOptionSlug = Newtype<string, 'OfferOptionSlug'>;\nexport const OfferOptionSlug = (value: string) => value as OfferOptionSlug;\n\nexport type OfferOption = {\n id: OfferOptionId;\n slug: OfferOptionSlug;\n bidIncrement: number | null;\n floorPriceUsd: number | null;\n minimumPurchaseUsd: number | null;\n priceUsd: number | null;\n saleAgreementUrl: string | null;\n totalTokenSupply: number | null;\n};\n\nexport type FaqItem = {\n question: string | null;\n answer: string | null;\n};\n\nexport type TermItem = {\n key: string | null;\n value: string | null;\n};\n\nexport type Milestone = {\n name: string | null;\n schedule: string | null;\n status: 'completed' | 'active' | 'upcoming';\n};\n\nexport type Link = {\n label: string | null;\n url: string | null;\n};\n\nexport const OfferDetail = {\n fromDto: (dto: OfferDetailDto): OfferDetail => {\n if (!Array.isArray(dto.funding_assets)) {\n throw new Error(`funding_assets must be an array`);\n }\n\n if (!Array.isArray(dto.options)) {\n throw new Error(`options must be an array`);\n }\n\n if (!Array.isArray(dto.terms)) {\n throw new Error(`terms must be an array`);\n }\n\n if (!Array.isArray(dto.links)) {\n throw new Error(`links must be an array`);\n }\n\n if (!Array.isArray(dto.faqs)) {\n throw new Error(`faqs must be an array`);\n }\n\n if (!Array.isArray(dto.milestones)) {\n throw new Error(`milestones must be an array`);\n }\n\n return {\n id: OfferId(dto.id),\n slug: OfferSlug(dto.slug),\n name: dto.name,\n\n asset: Asset.fromDto(dto.asset),\n fundingAssets: dto.funding_assets.map(Asset.fromDto),\n\n about: notBlankStringOrNull(dto.about),\n tagline: notBlankStringOrNull(dto.tagline),\n bannerUrl: notBlankStringOrNull(dto.banner_url),\n logoUrl: notBlankStringOrNull(dto.logo_url),\n category: notBlankStringOrNull(dto.category),\n\n startsAt: new Date(dto.starts_at),\n endsAt: new Date(dto.ends_at),\n\n faqs: dto.faqs.map(FaqItem.fromDto),\n links: dto.links.map(Link.fromDto),\n milestones: dto.milestones.map(Milestone.fromDto),\n options: dto.options.map(OfferOption.fromDto),\n terms: dto.terms.map(TermItem.fromDto),\n };\n },\n};\n\nexport const OfferOption = {\n fromDto: (dto: OfferDetailOptionDto): OfferOption => ({\n id: OfferOptionId(dto.id),\n slug: OfferOptionSlug(dto.slug),\n bidIncrement: dto.bid_increment,\n floorPriceUsd: dto.floor_price_usd,\n minimumPurchaseUsd: dto.minimum_purchase_usd,\n priceUsd: dto.price_usd,\n saleAgreementUrl: notBlankStringOrNull(dto.sale_agreement_url),\n totalTokenSupply: dto.total_token_supply,\n }),\n};\n\nexport const FaqItem = {\n fromDto: (dto: OfferDetailFaqDto): FaqItem => ({\n question: notBlankStringOrNull(dto.question),\n answer: notBlankStringOrNull(dto.answer),\n }),\n};\n\nexport const Link = {\n fromDto: (dto: OfferDetailLinkDto): Link => ({\n label: notBlankStringOrNull(dto.label),\n url: notBlankStringOrNull(dto.url),\n }),\n};\n\nexport const TermItem = {\n fromDto: (dto: OfferDetailTermDto): TermItem => ({\n key: notBlankStringOrNull(dto.key),\n value: notBlankStringOrNull(dto.value),\n }),\n};\n\nexport const Milestone = {\n fromDto: (dto: OfferDetailMilestoneDto): Milestone => ({\n name: notBlankStringOrNull(dto.name),\n schedule: notBlankStringOrNull(dto.schedule),\n status: dto.status,\n }),\n};\n","import type { HttpRequest } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/shared/api/http-client';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport function attachSessionMiddleware(\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n): BeforeRequestMiddleware {\n return async (request: HttpRequest): Promise<HttpRequest> => {\n if (!Attributes.isProtected(request.attributes)) {\n return request;\n }\n\n const accessToken = await fetchAccessToken(false);\n if (accessToken === null) {\n return request;\n }\n\n return {\n ...request,\n headers: {\n ...(request.headers ?? {}),\n Authorization: `Bearer ${accessToken.value}`,\n },\n };\n };\n}\n","import { Request } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { AfterRequestMiddleware } from '@/shared/api/http-client';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport function renewSessionMiddleware(\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n): AfterRequestMiddleware {\n return async ({ request, response, retry }) => {\n if (response.status !== 401) {\n return response;\n }\n if (!Attributes.isProtected(request.attributes)) {\n return response;\n }\n if (Attributes.wasRenewAttempted(request.attributes)) {\n return response;\n }\n\n const newToken = await fetchAccessToken(true);\n if (newToken) {\n const nextRequest = Request.concatAttributes(\n request,\n Attributes.renewAttempted(true)\n );\n return retry(nextRequest);\n } else {\n return response;\n }\n };\n}\n","'use client';\n\nimport { getUUIDv4 } from '@/shared/utils';\n\nconst DEVICE_ID_KEY = 'coinlist.device_id';\n\n/**\n * Get or create a device ID stored in localStorage.\n * Uses a crypto UUID that persists across sessions.\n */\nexport function getDeviceId(): string {\n let deviceId = localStorage.getItem(DEVICE_ID_KEY);\n\n if (!deviceId) {\n deviceId = getUUIDv4();\n localStorage.setItem(DEVICE_ID_KEY, deviceId);\n }\n\n return deviceId;\n}\n\n/**\n * Clear the stored device ID (useful for testing or logout).\n */\nexport function clearDeviceId(): void {\n localStorage.removeItem(DEVICE_ID_KEY);\n}\n","'use client';\n\nimport { getDeviceId } from '@/client/core/api/middleware/device-id';\nimport { HEADER_USER_AGENT } from '@/shared/api/frontline';\nimport type { HttpRequest } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/shared/api/http-client';\n\nexport const userAgentMiddleware: BeforeRequestMiddleware = async (\n request: HttpRequest\n): Promise<HttpRequest> => {\n if (!Attributes.needUserAgent(request.attributes)) {\n return request;\n }\n\n const userAgent = generateUserAgent();\n return {\n ...request,\n headers: {\n ...(request.headers ?? {}),\n [HEADER_USER_AGENT]: userAgent,\n },\n };\n};\n\n// Generate User-Agent in the required format for Frontline API\n// Format: <app-info> (<device-info>; DevicePlatform <device-platform>; DeviceAppID <device-app-id>; DeviceID <device-id>)\nfunction generateUserAgent(): string {\n const appInfo = `CoinList/1.0`;\n const deviceInfo = 'Web Browser';\n const devicePlatform = 'DevicePlatform web'; // Must be 'web' for validation\n\n const deviceAppId = 'DeviceAppID co.coinlist.sdk'; // SDK app ID\n\n const deviceId = getDeviceId();\n const finalDeviceId = `DeviceID ${deviceId}`;\n\n return `${appInfo} (${deviceInfo}; ${devicePlatform}; ${deviceAppId}; ${finalDeviceId})`;\n}\n","import { HEADER_IDEMPOTENCY_KEY } from '@/shared/api/frontline';\nimport type { HttpRequest } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/shared/api/http-client';\nimport { getUUIDv4 } from '@/shared/utils';\n\nexport const idempotencyKeyMiddleware: BeforeRequestMiddleware = async (\n request: HttpRequest\n): Promise<HttpRequest> => {\n if (!Attributes.isIdempotent(request.attributes)) {\n return request;\n }\n\n const existingHeaders = request.headers ?? {};\n if (existingHeaders[HEADER_IDEMPOTENCY_KEY]) {\n return request;\n }\n\n return {\n ...request,\n headers: {\n ...existingHeaders,\n [HEADER_IDEMPOTENCY_KEY]: getUUIDv4(),\n },\n };\n};\n","'use client';\n\nimport { attachSessionMiddleware } from '@/client/core/api/middleware/attach-session-middleware';\nimport { renewSessionMiddleware } from '@/client/core/api/middleware/session-renewal';\nimport { userAgentMiddleware } from '@/client/core/api/middleware/user-agent';\nimport {\n type HttpClientConfig,\n HttpError,\n type HttpRequest,\n} from '@/shared/api/http';\nimport { HttpClient } from '@/shared/api/http-client';\nimport { idempotencyKeyMiddleware } from '@/shared/api/middleware/idempotency-key';\nimport { requestRetryMiddleware } from '@/shared/api/middleware/request-retry';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport class ApiClient {\n private readonly httpClient: HttpClient;\n\n constructor(\n readonly config: HttpClientConfig,\n readonly fetchAccessToken: (\n refresh: boolean\n ) => Promise<OAuthAccessToken | null>\n ) {\n this.httpClient = new HttpClient(this.config, {\n beforeRequest: [\n attachSessionMiddleware(this.fetchAccessToken),\n userAgentMiddleware,\n idempotencyKeyMiddleware,\n ],\n afterRequest: [\n renewSessionMiddleware(this.fetchAccessToken),\n requestRetryMiddleware,\n ],\n });\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n const response = await this.httpClient.send<TResponse>(request);\n if (response.status >= 200 && response.status < 300) {\n return response.body as TResponse;\n } else {\n throw new HttpError(response);\n }\n }\n}\n","/**\n * Error thrown when a feature or code path is not yet implemented.\n */\nexport class NotImplementedError extends Error {\n constructor(message = 'Not implemented yet') {\n super(message);\n this.name = 'NotImplementedError';\n }\n}\n\n/**\n * Error thrown when accessing a feature that requires authentication\n * without being authenticated.\n */\nexport class NotAuthenticatedError extends Error {\n constructor(\n message = 'The user is not authenticated. Go through the OAuth flow first!'\n ) {\n super(message);\n this.name = 'NotAuthenticatedError';\n }\n}\n","'use client';\n\nimport { ApiClient } from '@/client/core/api/api-client';\nimport type {\n AuthState,\n OauthClientResult,\n} from '@/client/core/types/client-types';\nimport {\n API_VERSION,\n COINLIST_OAUTH_PAGE_URL,\n PUBLIC_API_BASE_URL,\n} from '@/shared/api/frontline';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport {\n fetchAllPages,\n PaginatedResponse,\n type PaginatedResponseDto,\n PaginationParams,\n} from '@/shared/api/pagination';\nimport type { Config } from '@/shared/types/config';\nimport type { OfferDto } from '@/shared/types/dto/offer';\nimport type { OfferDetailDto } from '@/shared/types/dto/offer-detail';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport { AuthorizationCode, CodeVerifier } from '@/shared/types/oauth';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\nimport { Offer, type OfferId } from '@/shared/types/offer';\nimport { OfferDetail } from '@/shared/types/offer-detail';\nimport {\n arrayBufferToBase64Url,\n generateSecureRandomBase64Url,\n sha256,\n} from '@/shared/utils';\n\n/** Session storage keys for OAuth PKCE (used by startOauth and completeOauth). */\nexport const OAUTH_STATE_KEY = 'coinlist.oauth_state';\nexport const OAUTH_CODE_VERIFIER_KEY = 'coinlist.oauth_code_verifier';\n\nexport interface ClientConfig extends Config {\n /**\n * The SDK user must provide this function. It is responsible for fetching a\n * valid {@link OAuthAccessToken}, or **null** for logged-out users. If your\n * backend is in TypeScript, it is recommended to use\n * {@link CoinListServer#accessToken} from `@/server/coinlist.server`.\n */\n getAccessToken: () => Promise<OAuthAccessToken | null>;\n /**\n * Recommended to leave undefined. Used to change the CoinList environment;\n * default is production.\n */\n authorizationPageUrl?: string;\n}\n\nexport interface CoinListClient {\n /**\n * Calls the provided {@link ClientConfig.getAccessToken} lambda to determine\n * the user's {@link AuthState}. This is called automatically by {@link CoinListProvider};\n * you typically do not need to call it yourself unless you are using the client\n * outside of the provider.\n */\n init(): Promise<AuthState>;\n /**\n * Returns the current auth state: `'logged-in'`, `'logged-out'`, or `'unknown'`.\n * Returns **unknown** until {@link CoinListClient.init} has completed. It is\n * recommended to use the `const { isReady, coinlist } = useCoinList()` hook to know when\n * the SDK has initialized, or to manually await {@link CoinListClient.init}\n * before relying on this value.\n */\n getAuthState(): AuthState;\n /**\n * Starts the OAuth flow with PKCE. Generates a random `state` and `code_verifier`,\n * stores them in sessionStorage (for validation in {@link CoinListClient.completeOAuth}\n * on redirect), then redirects the user to the CoinList authorization page. After\n * the user authorizes, they are sent back to your application's redirect URI; call\n * {@link CoinListClient.completeOAuth} on that page to finish the flow.\n */\n startOAuth(): Promise<void>;\n /**\n * Completes the OAuth flow on the FE with PKCE and state validation. Parses the\n * redirect URL (state, code) and validates them against sessionStorage\n * (state, code_verifier). The state check protects against CSRF (cross-site\n * request forgery). Call this on the frontend page the user is redirected\n * to after authorizing.\n *\n * On `ok`: Call your backend to exchange the short-lived `authorizationCode`\n * (and the returned `codeVerifier`) for a long-lived session. If your backend\n * is in TypeScript, you can use {@link CoinListServer#completeOAuth} from\n * `@/server/coinlist.server` (CoinListServer) to perform that exchange.\n *\n * On `error`: `reason` is an {@link OauthClientErrorReason} (e.g. `'missing_state'`, `'invalid_state'`) so you can switch on it and surface a message to the user.\n */\n completeOAuth(): OauthClientResult;\n /**\n * Clears the in-memory access token. Note: you need to manually\n * also logout on the backend via CoinListServer#logout to clear the session cookie.\n */\n logout(): void;\n\n /**\n * Fetches all offers by iterating through every paginated response.\n *\n * This method must be called only when the user's {@link AuthState} is\n * `'logged-in'`. If the user is not authenticated, it throws\n * {@link NotAuthenticatedError}.\n */\n fetchAllOffers(): Promise<Offer[]>;\n /**\n * Fetches a single page of offers.\n *\n * This method must be called only when the user's {@link AuthState} is\n * `'logged-in'`. If the user is not authenticated, it throws\n * {@link NotAuthenticatedError}.\n */\n fetchOffersPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;\n /**\n * Fetches details for a given offer by its id.\n *\n * This method must be called only when the user's {@link AuthState} is\n * `'logged-in'`. If the user is not authenticated, it throws\n * {@link NotAuthenticatedError}.\n */\n fetchOfferDetails(id: OfferId): Promise<OfferDetail>;\n}\n\nclass CoinListClientImpl implements CoinListClient {\n /** Cached access token: undefined = loading, null = no token, AccessToken = logged in */\n private _accessToken: OAuthAccessToken | null | undefined = undefined;\n private readonly api: ApiClient;\n\n constructor(readonly _config: ClientConfig) {\n this.api = new ApiClient(\n {\n baseUrl: _config.baseUrl || PUBLIC_API_BASE_URL,\n xApiVersion: API_VERSION,\n },\n this.fetchAccessToken.bind(this)\n );\n }\n\n async init(): Promise<AuthState> {\n await this.fetchAccessToken(true);\n return this.getAuthState();\n }\n\n async fetchAccessToken(refresh: boolean): Promise<OAuthAccessToken | null> {\n if (!refresh && this._accessToken !== undefined) return this._accessToken;\n\n try {\n const accessToken = await this._config.getAccessToken();\n this._accessToken = accessToken;\n return accessToken;\n } catch (_) {\n this._accessToken = null;\n return null;\n }\n }\n\n getAuthState(): AuthState {\n if (this._accessToken === undefined) {\n return 'unknown';\n }\n return this._accessToken !== null ? 'logged-in' : 'logged-out';\n }\n\n async startOAuth(): Promise<void> {\n const state = generateSecureRandomBase64Url(32);\n const codeVerifier = generateSecureRandomBase64Url(32);\n\n const codeChallengeRaw = await sha256(codeVerifier);\n const codeChallenge = arrayBufferToBase64Url(codeChallengeRaw, false);\n\n sessionStorage.setItem(OAUTH_STATE_KEY, state);\n sessionStorage.setItem(OAUTH_CODE_VERIFIER_KEY, codeVerifier);\n\n const params = new URLSearchParams({\n client_id: this._config.clientId,\n response_type: 'code',\n redirect_uri: this._config.redirectUri,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state,\n });\n const oauthPageUrl =\n this._config.authorizationPageUrl ?? COINLIST_OAUTH_PAGE_URL;\n const url = `${oauthPageUrl}?${params.toString()}`;\n window.location.href = url;\n }\n\n completeOAuth(): OauthClientResult {\n const params = new URLSearchParams(window.location.search);\n\n const coinlistError = params.get('error');\n if (coinlistError) {\n return { type: 'error', reason: 'user_canceled' };\n }\n\n const stateParam = params.get('state');\n if (!stateParam) {\n return { type: 'error', reason: 'missing_state' };\n }\n const storedState = sessionStorage.getItem(OAUTH_STATE_KEY);\n if (storedState === null || storedState !== stateParam) {\n return { type: 'error', reason: 'invalid_state' };\n }\n\n const codeParam = params.get('code');\n if (!codeParam) {\n return { type: 'error', reason: 'missing_code' };\n }\n\n const codeVerifierValue = sessionStorage.getItem(OAUTH_CODE_VERIFIER_KEY);\n if (!codeVerifierValue) {\n return { type: 'error', reason: 'missing_code_verifier' };\n }\n\n return {\n type: 'ok',\n code: AuthorizationCode(codeParam),\n codeVerifier: CodeVerifier(codeVerifierValue),\n };\n }\n\n logout(): void {\n this._accessToken = null;\n }\n\n async fetchAllOffers(): Promise<Offer[]> {\n this.ensureAuthenticated();\n return await fetchAllPages(this.fetchOffersPage.bind(this));\n }\n\n async fetchOffersPage(\n params: PaginationParams\n ): Promise<PaginatedResponse<Offer>> {\n this.ensureAuthenticated();\n const pageDto = await this.api.send<PaginatedResponseDto<OfferDto>>({\n method: 'GET',\n url: '/v1/offers',\n queryParams: PaginationParams.toQueryParams(params),\n attributes: Attributes.protected(),\n });\n return PaginatedResponse.fromDto(pageDto, Offer.fromDto);\n }\n\n async fetchOfferDetails(id: OfferId): Promise<OfferDetail> {\n this.ensureAuthenticated();\n const offerDetailDto = await this.api.send<OfferDetailDto>({\n method: 'GET',\n url: `/v1/offers/${id}`,\n attributes: Attributes.protected(),\n });\n return OfferDetail.fromDto(offerDetailDto);\n }\n\n private ensureAuthenticated() {\n if (this.getAuthState() !== 'logged-in') {\n throw new NotAuthenticatedError();\n }\n }\n}\n\nexport function createCoinListClient(config: ClientConfig): CoinListClient {\n return new CoinListClientImpl(config);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,118 @@
1
+ import {
2
+ createCoinListClient
3
+ } from "./chunk-6CG7JT7Q.js";
4
+
5
+ // src/client/CoinListProvider.tsx
6
+ import {
7
+ createContext,
8
+ useEffect,
9
+ useMemo,
10
+ useState
11
+ } from "react";
12
+ import { jsx } from "react/jsx-runtime";
13
+ var CoinListContext = createContext(null);
14
+ function CoinListProvider({ config, children }) {
15
+ const [isReady, setIsReady] = useState(false);
16
+ const coinlist = useMemo(() => createCoinListClient(config), [config]);
17
+ useEffect(() => {
18
+ let isCancelled = false;
19
+ if (coinlist.getAuthState() === "unknown") {
20
+ coinlist.init().then(() => {
21
+ if (!isCancelled) {
22
+ setIsReady(true);
23
+ }
24
+ }).catch(() => {
25
+ if (!isCancelled) {
26
+ setIsReady(true);
27
+ }
28
+ });
29
+ } else {
30
+ setIsReady(true);
31
+ }
32
+ return () => {
33
+ isCancelled = true;
34
+ };
35
+ }, [coinlist]);
36
+ const value = useMemo(() => ({ coinlist, isReady }), [coinlist, isReady]);
37
+ return /* @__PURE__ */ jsx(CoinListContext.Provider, { value, children });
38
+ }
39
+
40
+ // src/client/hooks/useCoinList.ts
41
+ import { useContext } from "react";
42
+ function useCoinList() {
43
+ const context = useContext(CoinListContext);
44
+ if (context === null) {
45
+ throw new CoinListClientInitializationError();
46
+ }
47
+ return {
48
+ isReady: context.isReady,
49
+ coinlist: context.coinlist
50
+ };
51
+ }
52
+ var CoinListClientInitializationError = class extends Error {
53
+ constructor(message = "useCoinList() must be used within CoinListProvider. Wrap your app with <CoinListProvider config={...}>.") {
54
+ super(message);
55
+ this.name = "CoinListClientInitializationError";
56
+ }
57
+ };
58
+
59
+ // src/client/hooks/useCompleteCoinListOAuth.ts
60
+ import { useEffect as useEffect2, useRef } from "react";
61
+ function useCompleteCoinListOAuth(options) {
62
+ const { coinlist } = useCoinList();
63
+ const postOAuthCompleteRef = useRef(options.postOAuthComplete);
64
+ const onFailureRef = useRef(options.onFailure);
65
+ const onSuccessRef = useRef(options.onSuccess);
66
+ postOAuthCompleteRef.current = options.postOAuthComplete;
67
+ onFailureRef.current = options.onFailure;
68
+ onSuccessRef.current = options.onSuccess;
69
+ const hasStartedExchangeRef = useRef(false);
70
+ useEffect2(() => {
71
+ if (typeof window === "undefined") return;
72
+ const params = new URLSearchParams(window.location.search);
73
+ if (!params.has("code") && !params.has("error")) return;
74
+ if (hasStartedExchangeRef.current) return;
75
+ const oauthResult = coinlist.completeOAuth();
76
+ if (oauthResult.type === "error") {
77
+ void Promise.resolve(onFailureRef.current(oauthResult.reason)).catch(
78
+ () => {
79
+ }
80
+ );
81
+ return;
82
+ }
83
+ hasStartedExchangeRef.current = true;
84
+ let cancelled = false;
85
+ void (async () => {
86
+ try {
87
+ const ok = await postOAuthCompleteRef.current({
88
+ code: oauthResult.code,
89
+ codeVerifier: oauthResult.codeVerifier
90
+ });
91
+ if (cancelled) return;
92
+ if (!ok) {
93
+ hasStartedExchangeRef.current = false;
94
+ await onFailureRef.current("complete_request_failed");
95
+ return;
96
+ }
97
+ await coinlist.init();
98
+ if (cancelled) return;
99
+ await onSuccessRef.current();
100
+ } catch {
101
+ if (cancelled) return;
102
+ hasStartedExchangeRef.current = false;
103
+ await onFailureRef.current("complete_request_failed");
104
+ }
105
+ })();
106
+ return () => {
107
+ cancelled = true;
108
+ hasStartedExchangeRef.current = false;
109
+ };
110
+ }, [coinlist]);
111
+ }
112
+
113
+ export {
114
+ CoinListProvider,
115
+ useCoinList,
116
+ useCompleteCoinListOAuth
117
+ };
118
+ //# sourceMappingURL=chunk-DLEKT4T4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/CoinListProvider.tsx","../src/client/hooks/useCoinList.ts","../src/client/hooks/useCompleteCoinListOAuth.ts"],"sourcesContent":["/**\n * Global CoinList initialization for React apps.\n * Wrap your app once at the root; use useCoinList() anywhere below.\n */\n\nimport {\n createContext,\n type ReactNode,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport {\n type ClientConfig,\n type CoinListClient,\n createCoinListClient,\n} from '@/client/core/coinlist-client';\n\nexport type CoinListContextValue = {\n coinlist: CoinListClient;\n isReady: boolean;\n};\n\nexport const CoinListContext = createContext<CoinListContextValue | null>(null);\n\nexport type CoinListProviderProps = {\n config: ClientConfig;\n children: ReactNode;\n};\n\n/**\n * Provide CoinList (api + config) to the tree. Initialize once at app root with your config.\n */\nexport function CoinListProvider({ config, children }: CoinListProviderProps) {\n const [isReady, setIsReady] = useState(false);\n const coinlist = useMemo(() => createCoinListClient(config), [config]);\n\n useEffect(() => {\n let isCancelled = false;\n if (coinlist.getAuthState() === 'unknown') {\n coinlist\n .init()\n .then(() => {\n if (!isCancelled) {\n setIsReady(true);\n }\n })\n .catch(() => {\n if (!isCancelled) {\n setIsReady(true);\n }\n });\n } else {\n setIsReady(true);\n }\n\n return () => {\n isCancelled = true;\n };\n }, [coinlist]);\n\n const value = useMemo(() => ({ coinlist, isReady }), [coinlist, isReady]);\n\n return (\n <CoinListContext.Provider value={value}>\n {children}\n </CoinListContext.Provider>\n );\n}\n","import { useContext } from 'react';\nimport { CoinListContext } from '@/client/CoinListProvider';\nimport type { CoinListClient } from '@/client/core/coinlist-client';\n\nexport interface UseCoinListResult {\n /**\n * Indicates whether the CoinList client has been initialized and ready to use.\n * While isReady is false, then the current user state will be `unknown`.\n */\n isReady: boolean;\n coinlist: CoinListClient;\n}\n\nexport function useCoinList(): UseCoinListResult {\n const context = useContext(CoinListContext);\n if (context === null) {\n throw new CoinListClientInitializationError();\n }\n\n return {\n isReady: context.isReady,\n coinlist: context.coinlist,\n };\n}\n\nexport class CoinListClientInitializationError extends Error {\n constructor(\n message = 'useCoinList() must be used within CoinListProvider. Wrap your app with <CoinListProvider config={...}>.'\n ) {\n super(message);\n this.name = 'CoinListClientInitializationError';\n }\n}\n","import { useEffect, useRef } from 'react';\nimport type { OauthClientErrorReason } from '@/client/core/types/client-types';\nimport { useCoinList } from '@/client/hooks/useCoinList';\nimport type { AuthorizationCode, CodeVerifier } from '@/shared/types/oauth';\n\n/**\n * Reasons passed to {@link UseCompleteCoinListOAuthOptions.onFailure} when the\n * OAuth callback cannot be completed. Includes PKCE / redirect validation\n * errors from {@link CoinListClient.completeOAuth} and a dedicated value when\n * {@link UseCompleteCoinListOAuthOptions.postOAuthComplete} fails or rejects.\n */\nexport type CompleteCoinListOAuthFailureReason =\n | OauthClientErrorReason\n | 'complete_request_failed';\n\nexport interface UseCompleteCoinListOAuthOptions {\n /**\n * Exchange the authorization code on your backend (e.g. POST to your API).\n * Return `true` when the session was established successfully, `false` when\n * the request completed but signing in failed. Rejections are treated like\n * `false` and reported as {@link CompleteCoinListOAuthFailureReason}\n * `\"complete_request_failed\"`.\n */\n postOAuthComplete: (payload: {\n code: AuthorizationCode;\n codeVerifier: CodeVerifier;\n }) => Promise<boolean>;\n /**\n * Called when the user denied access (`error` query parameter), when\n * {@link CoinListClient.completeOAuth} fails PKCE/state validation, or when\n * {@link UseCompleteCoinListOAuthOptions.postOAuthComplete} fails.\n */\n onFailure: (\n reason: CompleteCoinListOAuthFailureReason\n ) => void | Promise<void>;\n /** Invoked after {@link UseCompleteCoinListOAuthOptions.postOAuthComplete} succeeds and {@link CoinListClient.init} has refreshed the client session. */\n onSuccess: () => void | Promise<void>;\n}\n\n/**\n * Runs the CoinList OAuth callback once on mount: validates the redirect URL via\n * {@link CoinListClient.completeOAuth}, exchanges the code via {@link UseCompleteCoinListOAuthOptions.postOAuthComplete},\n * refreshes tokens with {@link CoinListClient.init}, then calls {@link UseCompleteCoinListOAuthOptions.onSuccess}.\n *\n * Framework-agnostic: reads `window.location` like {@link CoinListClient.completeOAuth}. Supply\n * {@link UseCompleteCoinListOAuthOptions.postOAuthComplete}, {@link UseCompleteCoinListOAuthOptions.onFailure},\n * and {@link UseCompleteCoinListOAuthOptions.onSuccess} to perform redirects or routing in your app.\n *\n * Must be used within {@link CoinListProvider}.\n */\nexport function useCompleteCoinListOAuth(\n options: UseCompleteCoinListOAuthOptions\n): void {\n const { coinlist } = useCoinList();\n\n const postOAuthCompleteRef = useRef(options.postOAuthComplete);\n const onFailureRef = useRef(options.onFailure);\n const onSuccessRef = useRef(options.onSuccess);\n\n postOAuthCompleteRef.current = options.postOAuthComplete;\n onFailureRef.current = options.onFailure;\n onSuccessRef.current = options.onSuccess;\n\n const hasStartedExchangeRef = useRef(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const params = new URLSearchParams(window.location.search);\n if (!params.has('code') && !params.has('error')) return;\n\n if (hasStartedExchangeRef.current) return;\n\n const oauthResult = coinlist.completeOAuth();\n if (oauthResult.type === 'error') {\n void Promise.resolve(onFailureRef.current(oauthResult.reason)).catch(\n () => {}\n );\n return;\n }\n\n hasStartedExchangeRef.current = true;\n let cancelled = false;\n\n void (async () => {\n try {\n const ok = await postOAuthCompleteRef.current({\n code: oauthResult.code,\n codeVerifier: oauthResult.codeVerifier,\n });\n\n if (cancelled) return;\n\n if (!ok) {\n hasStartedExchangeRef.current = false;\n await onFailureRef.current('complete_request_failed');\n return;\n }\n\n await coinlist.init();\n\n if (cancelled) return;\n\n await onSuccessRef.current();\n } catch {\n if (cancelled) return;\n hasStartedExchangeRef.current = false;\n await onFailureRef.current('complete_request_failed');\n }\n })();\n\n return () => {\n cancelled = true;\n hasStartedExchangeRef.current = false;\n };\n }, [coinlist]);\n}\n"],"mappings":"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","names":["useEffect","useEffect"]}
@@ -73,7 +73,10 @@ async function makeRequest(request) {
73
73
  if (request.method === "POST" && request.body !== void 0) {
74
74
  init.body = JSON.stringify(request.body);
75
75
  }
76
- const response = await fetch(request.url, init);
76
+ const response = await fetch(
77
+ buildUrlWithQueryParams(request.url, request.queryParams),
78
+ init
79
+ );
77
80
  const responseHeaders = headersToRecord(response.headers);
78
81
  if (response.status === 204 || response.status === 205) {
79
82
  return {
@@ -98,6 +101,32 @@ async function makeRequest(request) {
98
101
  headers: responseHeaders
99
102
  };
100
103
  }
104
+ function buildUrlWithQueryParams(url, queryParams) {
105
+ if (!queryParams) {
106
+ return url;
107
+ }
108
+ const searchParams = new URLSearchParams();
109
+ for (const [key, value] of Object.entries(queryParams)) {
110
+ if (value === void 0 || value === null) {
111
+ continue;
112
+ }
113
+ if (Array.isArray(value)) {
114
+ for (const item of value) {
115
+ if (item === void 0 || item === null) {
116
+ continue;
117
+ }
118
+ searchParams.append(key, String(item));
119
+ }
120
+ continue;
121
+ }
122
+ searchParams.append(key, String(value));
123
+ }
124
+ const queryString = searchParams.toString();
125
+ if (!queryString) {
126
+ return url;
127
+ }
128
+ return url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
129
+ }
101
130
  function headersToRecord(headers) {
102
131
  const record = {};
103
132
  headers.forEach((value, key) => {
@@ -126,7 +155,7 @@ var HEADER_API_VERSION = "X-API-Version";
126
155
  var HEADER_USER_AGENT = "User-Agent";
127
156
  var HEADER_IDEMPOTENCY_KEY = "Idempotency-Key";
128
157
  var API_VERSION = "2025-10-17";
129
- var COINLIST_OAUTH_URL = "https://coinlist.co/oauth/authorize";
158
+ var COINLIST_OAUTH_PAGE_URL = "https://coinlist.co/oauth/authorize";
130
159
 
131
160
  // src/shared/api/http-client.ts
132
161
  var HttpClient = class {
@@ -240,7 +269,7 @@ export {
240
269
  HEADER_USER_AGENT,
241
270
  HEADER_IDEMPOTENCY_KEY,
242
271
  API_VERSION,
243
- COINLIST_OAUTH_URL,
272
+ COINLIST_OAUTH_PAGE_URL,
244
273
  HttpClient,
245
274
  requestRetryMiddleware,
246
275
  AuthorizationCode,
@@ -250,4 +279,4 @@ export {
250
279
  ClientId,
251
280
  ClientSecret
252
281
  };
253
- //# sourceMappingURL=chunk-W6ZBUBLA.js.map
282
+ //# sourceMappingURL=chunk-GRVV7NLV.js.map