@coinlist-co/react 0.5.0 → 0.6.0

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.
@@ -1,8 +1,27 @@
1
- import { A as AuthorizationCode, C as CodeVerifier, n as OAuthSession, O as OAuthAccessToken, b as Offer, P as PaginationParams, c as PaginatedResponse, d as OfferId, e as OfferDetail, f as Participation, g as ParticipationsPaginationParams, h as ParticipationId, i as CreateParticipationParams, j as OfferOptionId, R as Requirement, k as RequirementStatusInfo, a as Config, o as ClientSecret } from '../requirement-BEO42QOr.cjs';
1
+ import { A as AuthorizationCode, C as CodeVerifier, n as OAuthSession, O as OAuthAccessToken, b as Offer, P as PaginationParams, c as PaginatedResponse, d as OfferId, e as OfferDetail, f as Participation, g as ParticipationsPaginationParams, h as ParticipationId, i as CreateParticipationParams, j as OfferOptionId, R as Requirement, k as RequirementStatusInfo, a as Config, o as ClientSecret } from '../requirement-Dk6nYN1c.cjs';
2
2
 
3
3
  interface SessionStore {
4
4
  getSession(): Promise<OAuthSession | null>;
5
- setSession(session: OAuthSession | null): Promise<void>;
5
+ /**
6
+ * Persists or clears the OAuth session.
7
+ *
8
+ * **Omit this method to create a read-only store.** When absent, the SDK
9
+ * skips token refresh entirely — no network call is made and no refresh
10
+ * token is consumed. This is the correct approach for contexts that can read
11
+ * the session but cannot write it back, such as Next.js Server Components.
12
+ *
13
+ * ⚠️ Do **not** implement this as a no-op (`async () => {}`). If the method
14
+ * is present, the SDK assumes writes succeed: it will fire a token refresh
15
+ * network call, consume the refresh token, then invoke `setSession` — which
16
+ * would silently discard the new session and leave the browser holding an
17
+ * invalidated refresh token. Simply **omit** `setSession` to prevent any
18
+ * refresh from being attempted.
19
+ *
20
+ * {@link CoinListServer.completeOAuth} and {@link CoinListServer.logout}
21
+ * always require a writable store and throw
22
+ * {@link WritableSessionStoreRequiredError} if `setSession` is absent.
23
+ */
24
+ setSession?(session: OAuthSession | null): Promise<void>;
6
25
  }
7
26
  interface ServerConfig extends Config {
8
27
  readonly clientSecret: ClientSecret;
@@ -15,19 +34,56 @@ interface ServerConfig extends Config {
15
34
  */
16
35
  readonly strict?: boolean;
17
36
  }
37
+ /**
38
+ * Server-side CoinList SDK client.
39
+ *
40
+ * Operates in one of two modes depending on whether {@link SessionStore}
41
+ * includes a `setSession` implementation:
42
+ *
43
+ * - **Writable store** (`setSession` provided) — full functionality: token
44
+ * refresh, {@link completeOAuth}, and {@link logout} all work normally.
45
+ *
46
+ * - **Read-only store** (no `setSession`) — token refresh is skipped entirely,
47
+ * meaning no network call is made and no refresh token is consumed.
48
+ * {@link completeOAuth} and {@link logout} throw
49
+ * {@link WritableSessionStoreRequiredError}. {@link accessToken} may return
50
+ * an expired token (see its docs). Use this mode in execution contexts that
51
+ * can read the session but cannot write it back, such as Next.js Server
52
+ * Components.
53
+ */
18
54
  interface CoinListServer {
55
+ /**
56
+ * Exchanges an authorization code for an OAuth session and persists it via
57
+ * {@link SessionStore.setSession}.
58
+ *
59
+ * Throws {@link WritableSessionStoreRequiredError} if the session store does
60
+ * not provide `setSession`.
61
+ */
19
62
  completeOAuth(code: AuthorizationCode, codeVerifier: CodeVerifier): Promise<OAuthSession>;
20
63
  /**
21
- * Responsible for:
22
- * 1. Returning a securely stored access token from Cookies
23
- * 2. Renewing the session if it's expired (or about to expire)
64
+ * Returns a valid access token for the current session, refreshing it if
65
+ * it is expired or near expiry.
66
+ *
67
+ * **Writable store**: if the token is expired, the SDK exchanges the refresh
68
+ * token for a new session, persists it, and returns the fresh access token.
69
+ * Returns `null` if there is no session or the refresh fails.
70
+ *
71
+ * **Read-only store** (no `setSession`): refresh is skipped entirely. The
72
+ * stored token is returned as-is, even if it is expired — a non-null return
73
+ * value does **not** guarantee the token is accepted by the API. Before
74
+ * making API calls, check `token.expiresAt > new Date()`. Use a writable
75
+ * store (e.g. in a Route Handler) when you need the SDK to renew the session
76
+ * automatically.
24
77
  *
25
- * @returns a valid access token or null if the user is not authentication.
26
- * On error rejects the Promise and throws.
78
+ * @returns the access token, or `null` if there is no session or the session
79
+ * could not be refreshed.
27
80
  */
28
81
  accessToken(): Promise<OAuthAccessToken | null>;
29
82
  /**
30
83
  * Revokes the current token via POST /oauth/revoke and clears the session.
84
+ *
85
+ * Throws {@link WritableSessionStoreRequiredError} if the session store does
86
+ * not provide `setSession`.
31
87
  */
32
88
  logout(): Promise<void>;
33
89
  /**
@@ -87,4 +143,8 @@ interface CoinListServer {
87
143
  }
88
144
  declare function createCoinListServer(config: ServerConfig): CoinListServer;
89
145
 
90
- export { type CoinListServer, type ServerConfig, type SessionStore, createCoinListServer };
146
+ declare class WritableSessionStoreRequiredError extends Error {
147
+ constructor(message?: string);
148
+ }
149
+
150
+ export { type CoinListServer, type ServerConfig, type SessionStore, WritableSessionStoreRequiredError, createCoinListServer };
@@ -1,8 +1,27 @@
1
- import { A as AuthorizationCode, C as CodeVerifier, n as OAuthSession, O as OAuthAccessToken, b as Offer, P as PaginationParams, c as PaginatedResponse, d as OfferId, e as OfferDetail, f as Participation, g as ParticipationsPaginationParams, h as ParticipationId, i as CreateParticipationParams, j as OfferOptionId, R as Requirement, k as RequirementStatusInfo, a as Config, o as ClientSecret } from '../requirement-BEO42QOr.js';
1
+ import { A as AuthorizationCode, C as CodeVerifier, n as OAuthSession, O as OAuthAccessToken, b as Offer, P as PaginationParams, c as PaginatedResponse, d as OfferId, e as OfferDetail, f as Participation, g as ParticipationsPaginationParams, h as ParticipationId, i as CreateParticipationParams, j as OfferOptionId, R as Requirement, k as RequirementStatusInfo, a as Config, o as ClientSecret } from '../requirement-Dk6nYN1c.js';
2
2
 
3
3
  interface SessionStore {
4
4
  getSession(): Promise<OAuthSession | null>;
5
- setSession(session: OAuthSession | null): Promise<void>;
5
+ /**
6
+ * Persists or clears the OAuth session.
7
+ *
8
+ * **Omit this method to create a read-only store.** When absent, the SDK
9
+ * skips token refresh entirely — no network call is made and no refresh
10
+ * token is consumed. This is the correct approach for contexts that can read
11
+ * the session but cannot write it back, such as Next.js Server Components.
12
+ *
13
+ * ⚠️ Do **not** implement this as a no-op (`async () => {}`). If the method
14
+ * is present, the SDK assumes writes succeed: it will fire a token refresh
15
+ * network call, consume the refresh token, then invoke `setSession` — which
16
+ * would silently discard the new session and leave the browser holding an
17
+ * invalidated refresh token. Simply **omit** `setSession` to prevent any
18
+ * refresh from being attempted.
19
+ *
20
+ * {@link CoinListServer.completeOAuth} and {@link CoinListServer.logout}
21
+ * always require a writable store and throw
22
+ * {@link WritableSessionStoreRequiredError} if `setSession` is absent.
23
+ */
24
+ setSession?(session: OAuthSession | null): Promise<void>;
6
25
  }
7
26
  interface ServerConfig extends Config {
8
27
  readonly clientSecret: ClientSecret;
@@ -15,19 +34,56 @@ interface ServerConfig extends Config {
15
34
  */
16
35
  readonly strict?: boolean;
17
36
  }
37
+ /**
38
+ * Server-side CoinList SDK client.
39
+ *
40
+ * Operates in one of two modes depending on whether {@link SessionStore}
41
+ * includes a `setSession` implementation:
42
+ *
43
+ * - **Writable store** (`setSession` provided) — full functionality: token
44
+ * refresh, {@link completeOAuth}, and {@link logout} all work normally.
45
+ *
46
+ * - **Read-only store** (no `setSession`) — token refresh is skipped entirely,
47
+ * meaning no network call is made and no refresh token is consumed.
48
+ * {@link completeOAuth} and {@link logout} throw
49
+ * {@link WritableSessionStoreRequiredError}. {@link accessToken} may return
50
+ * an expired token (see its docs). Use this mode in execution contexts that
51
+ * can read the session but cannot write it back, such as Next.js Server
52
+ * Components.
53
+ */
18
54
  interface CoinListServer {
55
+ /**
56
+ * Exchanges an authorization code for an OAuth session and persists it via
57
+ * {@link SessionStore.setSession}.
58
+ *
59
+ * Throws {@link WritableSessionStoreRequiredError} if the session store does
60
+ * not provide `setSession`.
61
+ */
19
62
  completeOAuth(code: AuthorizationCode, codeVerifier: CodeVerifier): Promise<OAuthSession>;
20
63
  /**
21
- * Responsible for:
22
- * 1. Returning a securely stored access token from Cookies
23
- * 2. Renewing the session if it's expired (or about to expire)
64
+ * Returns a valid access token for the current session, refreshing it if
65
+ * it is expired or near expiry.
66
+ *
67
+ * **Writable store**: if the token is expired, the SDK exchanges the refresh
68
+ * token for a new session, persists it, and returns the fresh access token.
69
+ * Returns `null` if there is no session or the refresh fails.
70
+ *
71
+ * **Read-only store** (no `setSession`): refresh is skipped entirely. The
72
+ * stored token is returned as-is, even if it is expired — a non-null return
73
+ * value does **not** guarantee the token is accepted by the API. Before
74
+ * making API calls, check `token.expiresAt > new Date()`. Use a writable
75
+ * store (e.g. in a Route Handler) when you need the SDK to renew the session
76
+ * automatically.
24
77
  *
25
- * @returns a valid access token or null if the user is not authentication.
26
- * On error rejects the Promise and throws.
78
+ * @returns the access token, or `null` if there is no session or the session
79
+ * could not be refreshed.
27
80
  */
28
81
  accessToken(): Promise<OAuthAccessToken | null>;
29
82
  /**
30
83
  * Revokes the current token via POST /oauth/revoke and clears the session.
84
+ *
85
+ * Throws {@link WritableSessionStoreRequiredError} if the session store does
86
+ * not provide `setSession`.
31
87
  */
32
88
  logout(): Promise<void>;
33
89
  /**
@@ -87,4 +143,8 @@ interface CoinListServer {
87
143
  }
88
144
  declare function createCoinListServer(config: ServerConfig): CoinListServer;
89
145
 
90
- export { type CoinListServer, type ServerConfig, type SessionStore, createCoinListServer };
146
+ declare class WritableSessionStoreRequiredError extends Error {
147
+ constructor(message?: string);
148
+ }
149
+
150
+ export { type CoinListServer, type ServerConfig, type SessionStore, WritableSessionStoreRequiredError, createCoinListServer };
@@ -12,13 +12,13 @@ import {
12
12
  fetchParticipations,
13
13
  fetchParticipationsPage,
14
14
  fetchRequirementStatuses
15
- } from "../chunk-N3WBC2VS.js";
15
+ } from "../chunk-5C4TEVM7.js";
16
16
  import {
17
17
  OAuthSession
18
18
  } from "../chunk-5E3P7AMH.js";
19
19
  import {
20
20
  NotAuthenticatedError
21
- } from "../chunk-V6WO67RO.js";
21
+ } from "../chunk-7SB2GKEU.js";
22
22
 
23
23
  // src/server/api/api.server.ts
24
24
  var Api = class {
@@ -30,6 +30,14 @@ var Api = class {
30
30
  }
31
31
  };
32
32
 
33
+ // src/server/errors.ts
34
+ var WritableSessionStoreRequiredError = class extends Error {
35
+ constructor(message = "This operation requires a writable SessionStore. Provide a `setSession` implementation in your SessionStore to persist or clear OAuth sessions.") {
36
+ super(message);
37
+ this.name = "WritableSessionStoreRequiredError";
38
+ }
39
+ };
40
+
33
41
  // src/server/coinlist.server.ts
34
42
  var ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;
35
43
  var CoinListServerImpl = class {
@@ -43,10 +51,19 @@ var CoinListServerImpl = class {
43
51
  baseUrl: this.baseUrl,
44
52
  xApiVersion: API_VERSION
45
53
  },
46
- (_refresh) => this.accessToken()
54
+ // When refresh=true the renewal middleware has received a 401 and wants a
55
+ // fresh token. A read-only store cannot persist a new session, so return
56
+ // null immediately — this tells the middleware to skip the retry rather
57
+ // than re-sending with the same expired token and wasting a round-trip.
58
+ (refresh) => refresh && !this._config.sessionStore.setSession ? Promise.resolve(null) : this.accessToken()
47
59
  );
48
60
  }
49
61
  async completeOAuth(code, codeVerifier) {
62
+ const sessionStore = this._config.sessionStore;
63
+ const setSession = sessionStore.setSession?.bind(sessionStore);
64
+ if (!setSession) {
65
+ throw new WritableSessionStoreRequiredError();
66
+ }
50
67
  const sessionDto = await this.api.send({
51
68
  method: "POST",
52
69
  url: `/oauth/token`,
@@ -60,11 +77,12 @@ var CoinListServerImpl = class {
60
77
  }
61
78
  });
62
79
  const session = OAuthSession.fromDto(sessionDto);
63
- this._config.sessionStore.setSession(session);
80
+ await setSession(session);
64
81
  return session;
65
82
  }
66
83
  async accessToken() {
67
- const session = await this._config.sessionStore.getSession();
84
+ const sessionStore = this._config.sessionStore;
85
+ const session = await sessionStore.getSession();
68
86
  if (session == null) return null;
69
87
  const now = Date.now();
70
88
  const expiresAt = session.accessToken.expiresAt.getTime();
@@ -72,9 +90,16 @@ var CoinListServerImpl = class {
72
90
  if (expiresAt > now + bufferMs) {
73
91
  return session.accessToken;
74
92
  }
75
- const refreshToken = session?.refreshToken;
93
+ const setSession = sessionStore.setSession?.bind(sessionStore);
94
+ if (!setSession) {
95
+ return session.accessToken;
96
+ } else {
97
+ return this.refreshSession(session.refreshToken, setSession);
98
+ }
99
+ }
100
+ async refreshSession(refreshToken, setSession) {
76
101
  if (!refreshToken) {
77
- await this._config.sessionStore.setSession(null);
102
+ await setSession(null);
78
103
  return null;
79
104
  }
80
105
  try {
@@ -89,15 +114,20 @@ var CoinListServerImpl = class {
89
114
  }
90
115
  });
91
116
  const newSession = OAuthSession.fromDto(sessionDto);
92
- await this._config.sessionStore.setSession(newSession);
117
+ await setSession(newSession);
93
118
  return newSession.accessToken;
94
119
  } catch {
95
- await this._config.sessionStore.setSession(null);
120
+ await setSession(null);
96
121
  return null;
97
122
  }
98
123
  }
99
124
  async logout() {
100
- const session = await this._config.sessionStore.getSession();
125
+ const sessionStore = this._config.sessionStore;
126
+ const setSession = sessionStore.setSession?.bind(sessionStore);
127
+ if (!setSession) {
128
+ throw new WritableSessionStoreRequiredError();
129
+ }
130
+ const session = await sessionStore.getSession();
101
131
  if (session != null) {
102
132
  const tokenToRevoke = session.accessToken.value;
103
133
  try {
@@ -119,7 +149,7 @@ var CoinListServerImpl = class {
119
149
  throw err;
120
150
  }
121
151
  }
122
- await this._config.sessionStore.setSession(null);
152
+ await setSession(null);
123
153
  }
124
154
  }
125
155
  async ensureAuthenticated() {
@@ -169,6 +199,7 @@ function createCoinListServer(config) {
169
199
  return new CoinListServerImpl(config);
170
200
  }
171
201
  export {
202
+ WritableSessionStoreRequiredError,
172
203
  createCoinListServer
173
204
  };
174
205
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/api/api.server.ts","../../src/server/coinlist.server.ts"],"sourcesContent":["import { AuthenticatedApiClient } from '@/shared/api/authenticated-api-client';\nimport type { HttpClientConfig, HttpRequest } from '@/shared/api/http';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport class Api {\n private readonly client: AuthenticatedApiClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n ) {\n this.client = new AuthenticatedApiClient(config, fetchAccessToken);\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n return this.client.send<TResponse>(request);\n }\n}\n","import { Api } from '@/server/api/api.server';\nimport {\n API_VERSION,\n PUBLIC_API_BASE_URL,\n} from '@/shared/api/frontline/config';\nimport * as offersApi from '@/shared/api/frontline/offers';\nimport * as participationsApi from '@/shared/api/frontline/participations';\nimport * as requirementsApi from '@/shared/api/frontline/requirements';\nimport { HttpError } from '@/shared/api/http';\nimport type {\n PaginatedResponse,\n PaginationParams,\n} from '@/shared/api/pagination';\nimport type { Config } from '@/shared/types/config';\nimport type { OAuthSessionDto } from '@/shared/types/dto/oauth-session';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type {\n AuthorizationCode,\n ClientSecret,\n CodeVerifier,\n} from '@/shared/types/oauth';\nimport {\n type OAuthAccessToken,\n OAuthSession,\n} from '@/shared/types/oauth-session';\nimport type { Offer, OfferId } from '@/shared/types/offer';\nimport type { OfferDetail, OfferOptionId } from '@/shared/types/offer-detail';\nimport type {\n CreateParticipationParams,\n Participation,\n ParticipationId,\n ParticipationsPaginationParams,\n} from '@/shared/types/participation';\nimport type {\n Requirement,\n RequirementStatusInfo,\n} from '@/shared/types/requirement';\n\n/** Buffer in seconds before expiry to consider token expired for refresh. */\nconst ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;\n\nexport interface SessionStore {\n getSession(): Promise<OAuthSession | null>;\n setSession(session: OAuthSession | null): Promise<void>;\n}\n\nexport interface ServerConfig extends Config {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /** Buffer in seconds before expiry to consider token expired for refresh. */\n readonly accessTokenExpiryBufferSeconds?: number;\n /**\n * Whether SDK will throw an exception in cases it can be silent.\n * For example, if token revokation on logout fails.\n */\n readonly strict?: boolean;\n}\n\nexport interface CoinListServer {\n completeOAuth(\n code: AuthorizationCode,\n codeVerifier: CodeVerifier\n ): Promise<OAuthSession>;\n\n /**\n * Responsible for:\n * 1. Returning a securely stored access token from Cookies\n * 2. Renewing the session if it's expired (or about to expire)\n *\n * @returns a valid access token or null if the user is not authentication.\n * On error rejects the Promise and throws.\n */\n accessToken(): Promise<OAuthAccessToken | null>;\n\n /**\n * Revokes the current token via POST /oauth/revoke and clears the session.\n */\n logout(): Promise<void>;\n\n /**\n * Fetches all offers by iterating through every paginated response.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOffers(): Promise<Offer[]>;\n\n /**\n * Fetches a single page of offers.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOffersPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;\n\n /**\n * Fetches details for a given offer by its id.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOfferDetails(id: OfferId): Promise<OfferDetail>;\n\n /**\n * Fetches all participations by iterating through every paginated response.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchParticipations(offerId?: OfferId): Promise<Participation[]>;\n\n /**\n * Fetches a single page of participations, optionally filtered by offer.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchParticipationsPage(\n params: ParticipationsPaginationParams\n ): Promise<PaginatedResponse<Participation>>;\n\n /**\n * Fetches a participation by id.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchParticipation(id: ParticipationId): Promise<Participation>;\n\n /**\n * Creates a participation.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n createParticipation(\n params: CreateParticipationParams\n ): Promise<Participation>;\n\n /**\n * Fetches the requirements for all options of a given offer, grouped by option ID.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOfferRequirements(\n offerId: OfferId\n ): Promise<Record<OfferOptionId, Requirement[]>>;\n\n /**\n * Fetches the user's requirement statuses for a given offer.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchRequirementStatuses(offerId: OfferId): Promise<RequirementStatusInfo[]>;\n}\n\nclass CoinListServerImpl implements CoinListServer {\n private readonly api: Api;\n private readonly baseUrl: string;\n\n private readonly accessTokenExpiryBufferSeconds: number;\n private readonly strict: boolean;\n\n constructor(private readonly _config: ServerConfig) {\n this.baseUrl = _config.baseUrl ?? PUBLIC_API_BASE_URL;\n this.accessTokenExpiryBufferSeconds =\n _config.accessTokenExpiryBufferSeconds ??\n ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS;\n this.strict = _config.strict ?? false;\n this.api = new Api(\n {\n baseUrl: this.baseUrl,\n xApiVersion: API_VERSION,\n },\n (_refresh) => this.accessToken()\n );\n }\n\n async completeOAuth(\n code: AuthorizationCode,\n codeVerifier: CodeVerifier\n ): Promise<OAuthSession> {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'authorization_code',\n code,\n redirect_uri: this._config.redirectUri,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n code_verifier: codeVerifier,\n },\n });\n const session = OAuthSession.fromDto(sessionDto);\n this._config.sessionStore.setSession(session);\n return session;\n }\n\n async accessToken(): Promise<OAuthAccessToken | null> {\n const session = await this._config.sessionStore.getSession();\n if (session == null) return null;\n\n const now = Date.now();\n const expiresAt = session.accessToken.expiresAt.getTime();\n const bufferMs = this.accessTokenExpiryBufferSeconds * 1000;\n if (expiresAt > now + bufferMs) {\n return session.accessToken;\n }\n\n const refreshToken = session?.refreshToken;\n if (!refreshToken) {\n await this._config.sessionStore.setSession(null);\n return null;\n }\n\n try {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n },\n });\n const newSession = OAuthSession.fromDto(sessionDto);\n await this._config.sessionStore.setSession(newSession);\n return newSession.accessToken;\n } catch {\n await this._config.sessionStore.setSession(null);\n return null;\n }\n }\n\n async logout(): Promise<void> {\n const session = await this._config.sessionStore.getSession();\n if (session != null) {\n const tokenToRevoke = session.accessToken.value;\n try {\n await this.api.send({\n method: 'POST',\n url: `/oauth/revoke`,\n body: {\n token: tokenToRevoke,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n },\n });\n } catch (err) {\n if (err instanceof HttpError) {\n if (this.strict) {\n throw err;\n }\n } else {\n throw err;\n }\n }\n await this._config.sessionStore.setSession(null);\n }\n }\n\n private async ensureAuthenticated(): Promise<void> {\n const token = await this.accessToken();\n if (token === null) {\n throw new NotAuthenticatedError();\n }\n }\n\n async fetchOffers(): Promise<Offer[]> {\n await this.ensureAuthenticated();\n return offersApi.fetchOffers(this.api);\n }\n\n async fetchOffersPage(\n params: PaginationParams\n ): Promise<PaginatedResponse<Offer>> {\n await this.ensureAuthenticated();\n return offersApi.fetchOffersPage(this.api, params);\n }\n\n async fetchOfferDetails(id: OfferId): Promise<OfferDetail> {\n await this.ensureAuthenticated();\n return offersApi.fetchOfferDetails(this.api, id);\n }\n\n async fetchParticipations(offerId?: OfferId): Promise<Participation[]> {\n await this.ensureAuthenticated();\n return participationsApi.fetchParticipations(this.api, offerId);\n }\n\n async fetchParticipationsPage(\n params: ParticipationsPaginationParams\n ): Promise<PaginatedResponse<Participation>> {\n await this.ensureAuthenticated();\n return participationsApi.fetchParticipationsPage(this.api, params);\n }\n\n async fetchParticipation(id: ParticipationId): Promise<Participation> {\n await this.ensureAuthenticated();\n return participationsApi.fetchParticipation(this.api, id);\n }\n\n async createParticipation(\n params: CreateParticipationParams\n ): Promise<Participation> {\n await this.ensureAuthenticated();\n return participationsApi.createParticipation(this.api, params);\n }\n\n async fetchOfferRequirements(\n offerId: OfferId\n ): Promise<Record<OfferOptionId, Requirement[]>> {\n await this.ensureAuthenticated();\n return requirementsApi.fetchOfferRequirements(this.api, offerId);\n }\n\n async fetchRequirementStatuses(\n offerId: OfferId\n ): Promise<RequirementStatusInfo[]> {\n await this.ensureAuthenticated();\n return requirementsApi.fetchRequirementStatuses(this.api, offerId);\n }\n}\n\nexport function createCoinListServer(config: ServerConfig): CoinListServer {\n return new CoinListServerImpl(config);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/server/api/api.server.ts","../../src/server/errors.ts","../../src/server/coinlist.server.ts"],"sourcesContent":["import { AuthenticatedApiClient } from '@/shared/api/authenticated-api-client';\nimport type { HttpClientConfig, HttpRequest } from '@/shared/api/http';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport class Api {\n private readonly client: AuthenticatedApiClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n ) {\n this.client = new AuthenticatedApiClient(config, fetchAccessToken);\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n return this.client.send<TResponse>(request);\n }\n}\n","export class WritableSessionStoreRequiredError extends Error {\n constructor(\n message = 'This operation requires a writable SessionStore. Provide a `setSession` implementation in your SessionStore to persist or clear OAuth sessions.'\n ) {\n super(message);\n this.name = 'WritableSessionStoreRequiredError';\n }\n}\n","import { Api } from '@/server/api/api.server';\nimport { WritableSessionStoreRequiredError } from '@/server/errors';\nimport {\n API_VERSION,\n PUBLIC_API_BASE_URL,\n} from '@/shared/api/frontline/config';\nimport * as offersApi from '@/shared/api/frontline/offers';\nimport * as participationsApi from '@/shared/api/frontline/participations';\nimport * as requirementsApi from '@/shared/api/frontline/requirements';\nimport { HttpError } from '@/shared/api/http';\nimport type {\n PaginatedResponse,\n PaginationParams,\n} from '@/shared/api/pagination';\nimport type { Config } from '@/shared/types/config';\nimport type { OAuthSessionDto } from '@/shared/types/dto/oauth-session';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type {\n AuthorizationCode,\n ClientSecret,\n CodeVerifier,\n} from '@/shared/types/oauth';\nimport {\n type OAuthAccessToken,\n type OAuthRefreshToken,\n OAuthSession,\n} from '@/shared/types/oauth-session';\nimport type { Offer, OfferId } from '@/shared/types/offer';\nimport type { OfferDetail, OfferOptionId } from '@/shared/types/offer-detail';\nimport type {\n CreateParticipationParams,\n Participation,\n ParticipationId,\n ParticipationsPaginationParams,\n} from '@/shared/types/participation';\nimport type {\n Requirement,\n RequirementStatusInfo,\n} from '@/shared/types/requirement';\n\n/** Buffer in seconds before expiry to consider token expired for refresh. */\nconst ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS = 60;\n\nexport interface SessionStore {\n getSession(): Promise<OAuthSession | null>;\n /**\n * Persists or clears the OAuth session.\n *\n * **Omit this method to create a read-only store.** When absent, the SDK\n * skips token refresh entirely — no network call is made and no refresh\n * token is consumed. This is the correct approach for contexts that can read\n * the session but cannot write it back, such as Next.js Server Components.\n *\n * ⚠️ Do **not** implement this as a no-op (`async () => {}`). If the method\n * is present, the SDK assumes writes succeed: it will fire a token refresh\n * network call, consume the refresh token, then invoke `setSession` — which\n * would silently discard the new session and leave the browser holding an\n * invalidated refresh token. Simply **omit** `setSession` to prevent any\n * refresh from being attempted.\n *\n * {@link CoinListServer.completeOAuth} and {@link CoinListServer.logout}\n * always require a writable store and throw\n * {@link WritableSessionStoreRequiredError} if `setSession` is absent.\n */\n setSession?(session: OAuthSession | null): Promise<void>;\n}\n\nexport interface ServerConfig extends Config {\n readonly clientSecret: ClientSecret;\n readonly sessionStore: SessionStore;\n /** Buffer in seconds before expiry to consider token expired for refresh. */\n readonly accessTokenExpiryBufferSeconds?: number;\n /**\n * Whether SDK will throw an exception in cases it can be silent.\n * For example, if token revokation on logout fails.\n */\n readonly strict?: boolean;\n}\n\n/**\n * Server-side CoinList SDK client.\n *\n * Operates in one of two modes depending on whether {@link SessionStore}\n * includes a `setSession` implementation:\n *\n * - **Writable store** (`setSession` provided) — full functionality: token\n * refresh, {@link completeOAuth}, and {@link logout} all work normally.\n *\n * - **Read-only store** (no `setSession`) — token refresh is skipped entirely,\n * meaning no network call is made and no refresh token is consumed.\n * {@link completeOAuth} and {@link logout} throw\n * {@link WritableSessionStoreRequiredError}. {@link accessToken} may return\n * an expired token (see its docs). Use this mode in execution contexts that\n * can read the session but cannot write it back, such as Next.js Server\n * Components.\n */\nexport interface CoinListServer {\n /**\n * Exchanges an authorization code for an OAuth session and persists it via\n * {@link SessionStore.setSession}.\n *\n * Throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n */\n completeOAuth(\n code: AuthorizationCode,\n codeVerifier: CodeVerifier\n ): Promise<OAuthSession>;\n\n /**\n * Returns a valid access token for the current session, refreshing it if\n * it is expired or near expiry.\n *\n * **Writable store**: if the token is expired, the SDK exchanges the refresh\n * token for a new session, persists it, and returns the fresh access token.\n * Returns `null` if there is no session or the refresh fails.\n *\n * **Read-only store** (no `setSession`): refresh is skipped entirely. The\n * stored token is returned as-is, even if it is expired — a non-null return\n * value does **not** guarantee the token is accepted by the API. Before\n * making API calls, check `token.expiresAt > new Date()`. Use a writable\n * store (e.g. in a Route Handler) when you need the SDK to renew the session\n * automatically.\n *\n * @returns the access token, or `null` if there is no session or the session\n * could not be refreshed.\n */\n accessToken(): Promise<OAuthAccessToken | null>;\n\n /**\n * Revokes the current token via POST /oauth/revoke and clears the session.\n *\n * Throws {@link WritableSessionStoreRequiredError} if the session store does\n * not provide `setSession`.\n */\n logout(): Promise<void>;\n\n /**\n * Fetches all offers by iterating through every paginated response.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOffers(): Promise<Offer[]>;\n\n /**\n * Fetches a single page of offers.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOffersPage(params: PaginationParams): Promise<PaginatedResponse<Offer>>;\n\n /**\n * Fetches details for a given offer by its id.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOfferDetails(id: OfferId): Promise<OfferDetail>;\n\n /**\n * Fetches all participations by iterating through every paginated response.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchParticipations(offerId?: OfferId): Promise<Participation[]>;\n\n /**\n * Fetches a single page of participations, optionally filtered by offer.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchParticipationsPage(\n params: ParticipationsPaginationParams\n ): Promise<PaginatedResponse<Participation>>;\n\n /**\n * Fetches a participation by id.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchParticipation(id: ParticipationId): Promise<Participation>;\n\n /**\n * Creates a participation.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n createParticipation(\n params: CreateParticipationParams\n ): Promise<Participation>;\n\n /**\n * Fetches the requirements for all options of a given offer, grouped by option ID.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchOfferRequirements(\n offerId: OfferId\n ): Promise<Record<OfferOptionId, Requirement[]>>;\n\n /**\n * Fetches the user's requirement statuses for a given offer.\n *\n * Throws {@link NotAuthenticatedError} if the user is not authenticated.\n */\n fetchRequirementStatuses(offerId: OfferId): Promise<RequirementStatusInfo[]>;\n}\n\nclass CoinListServerImpl implements CoinListServer {\n private readonly api: Api;\n private readonly baseUrl: string;\n\n private readonly accessTokenExpiryBufferSeconds: number;\n private readonly strict: boolean;\n\n constructor(private readonly _config: ServerConfig) {\n this.baseUrl = _config.baseUrl ?? PUBLIC_API_BASE_URL;\n this.accessTokenExpiryBufferSeconds =\n _config.accessTokenExpiryBufferSeconds ??\n ACCESS_TOKEN_EXPIRY_BUFFER_SECONDS;\n this.strict = _config.strict ?? false;\n this.api = new Api(\n {\n baseUrl: this.baseUrl,\n xApiVersion: API_VERSION,\n },\n // When refresh=true the renewal middleware has received a 401 and wants a\n // fresh token. A read-only store cannot persist a new session, so return\n // null immediately — this tells the middleware to skip the retry rather\n // than re-sending with the same expired token and wasting a round-trip.\n (refresh) =>\n refresh && !this._config.sessionStore.setSession\n ? Promise.resolve(null)\n : this.accessToken()\n );\n }\n\n async completeOAuth(\n code: AuthorizationCode,\n codeVerifier: CodeVerifier\n ): Promise<OAuthSession> {\n const sessionStore = this._config.sessionStore;\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n throw new WritableSessionStoreRequiredError();\n }\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'authorization_code',\n code,\n redirect_uri: this._config.redirectUri,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n code_verifier: codeVerifier,\n },\n });\n const session = OAuthSession.fromDto(sessionDto);\n await setSession(session);\n return session;\n }\n\n async accessToken(): Promise<OAuthAccessToken | null> {\n const sessionStore = this._config.sessionStore;\n const session = await sessionStore.getSession();\n if (session == null) return null;\n\n const now = Date.now();\n const expiresAt = session.accessToken.expiresAt.getTime();\n const bufferMs = this.accessTokenExpiryBufferSeconds * 1000;\n if (expiresAt > now + bufferMs) {\n // Valid access token, return it regardless\n return session.accessToken;\n }\n\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n // No write session capabilities => can't refresh!\n // Return the access token as-is\n return session.accessToken;\n } else {\n return this.refreshSession(session.refreshToken, setSession);\n }\n }\n\n private async refreshSession(\n refreshToken: OAuthRefreshToken | undefined,\n setSession: (session: OAuthSession | null) => Promise<void>\n ): Promise<OAuthAccessToken | null> {\n if (!refreshToken) {\n await setSession(null);\n return null;\n }\n\n try {\n const sessionDto = await this.api.send<OAuthSessionDto>({\n method: 'POST',\n url: `/oauth/token`,\n body: {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n },\n });\n const newSession = OAuthSession.fromDto(sessionDto);\n await setSession(newSession);\n return newSession.accessToken;\n } catch {\n await setSession(null);\n return null;\n }\n }\n\n async logout(): Promise<void> {\n const sessionStore = this._config.sessionStore;\n const setSession = sessionStore.setSession?.bind(sessionStore);\n if (!setSession) {\n throw new WritableSessionStoreRequiredError();\n }\n const session = await sessionStore.getSession();\n if (session != null) {\n const tokenToRevoke = session.accessToken.value;\n try {\n await this.api.send({\n method: 'POST',\n url: `/oauth/revoke`,\n body: {\n token: tokenToRevoke,\n client_id: this._config.clientId,\n client_secret: this._config.clientSecret,\n },\n });\n } catch (err) {\n if (err instanceof HttpError) {\n if (this.strict) {\n throw err;\n }\n } else {\n throw err;\n }\n }\n // invalidate the session\n await setSession(null);\n }\n }\n\n private async ensureAuthenticated(): Promise<void> {\n const token = await this.accessToken();\n if (token === null) {\n throw new NotAuthenticatedError();\n }\n }\n\n async fetchOffers(): Promise<Offer[]> {\n await this.ensureAuthenticated();\n return offersApi.fetchOffers(this.api);\n }\n\n async fetchOffersPage(\n params: PaginationParams\n ): Promise<PaginatedResponse<Offer>> {\n await this.ensureAuthenticated();\n return offersApi.fetchOffersPage(this.api, params);\n }\n\n async fetchOfferDetails(id: OfferId): Promise<OfferDetail> {\n await this.ensureAuthenticated();\n return offersApi.fetchOfferDetails(this.api, id);\n }\n\n async fetchParticipations(offerId?: OfferId): Promise<Participation[]> {\n await this.ensureAuthenticated();\n return participationsApi.fetchParticipations(this.api, offerId);\n }\n\n async fetchParticipationsPage(\n params: ParticipationsPaginationParams\n ): Promise<PaginatedResponse<Participation>> {\n await this.ensureAuthenticated();\n return participationsApi.fetchParticipationsPage(this.api, params);\n }\n\n async fetchParticipation(id: ParticipationId): Promise<Participation> {\n await this.ensureAuthenticated();\n return participationsApi.fetchParticipation(this.api, id);\n }\n\n async createParticipation(\n params: CreateParticipationParams\n ): Promise<Participation> {\n await this.ensureAuthenticated();\n return participationsApi.createParticipation(this.api, params);\n }\n\n async fetchOfferRequirements(\n offerId: OfferId\n ): Promise<Record<OfferOptionId, Requirement[]>> {\n await this.ensureAuthenticated();\n return requirementsApi.fetchOfferRequirements(this.api, offerId);\n }\n\n async fetchRequirementStatuses(\n offerId: OfferId\n ): Promise<RequirementStatusInfo[]> {\n await this.ensureAuthenticated();\n return requirementsApi.fetchRequirementStatuses(this.api, offerId);\n }\n}\n\nexport function createCoinListServer(config: ServerConfig): CoinListServer {\n return new CoinListServerImpl(config);\n}\n"],"mappings":"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","names":[]}
@@ -45,6 +45,7 @@ __export(shared_exports, {
45
45
  OfferOptionId: () => OfferOptionId,
46
46
  OfferOptionSlug: () => OfferOptionSlug,
47
47
  OfferSlug: () => OfferSlug,
48
+ PKCEState: () => PKCEState,
48
49
  PaginatedResponse: () => PaginatedResponse,
49
50
  PaginationParams: () => PaginationParams,
50
51
  Participation: () => Participation,
@@ -59,7 +60,8 @@ __export(shared_exports, {
59
60
  UserEmail: () => UserEmail,
60
61
  UserId: () => UserId,
61
62
  WalletAddress: () => WalletAddress,
62
- fetchAllPages: () => fetchAllPages
63
+ fetchAllPages: () => fetchAllPages,
64
+ generatePKCEParams: () => generatePKCEParams
63
65
  });
64
66
  module.exports = __toCommonJS(shared_exports);
65
67
 
@@ -102,6 +104,68 @@ var PaginationParams = {
102
104
  }
103
105
  };
104
106
 
107
+ // src/shared/types/oauth.ts
108
+ var AuthorizationCode = (value) => value;
109
+ var CodeVerifier = (value) => value;
110
+ var CodeChallenge = (value) => value;
111
+ var PKCEState = (value) => value;
112
+ var RedirectUri = (value) => value;
113
+ var ClientId = (value) => value;
114
+ var ClientSecret = (value) => value;
115
+
116
+ // src/shared/utils.ts
117
+ async function sha256(data) {
118
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
119
+ const buffer = bytes.buffer.slice(
120
+ bytes.byteOffset,
121
+ bytes.byteOffset + bytes.byteLength
122
+ );
123
+ return crypto.subtle.digest("SHA-256", buffer);
124
+ }
125
+ function arrayBufferToBase64Url(buffer, padding = true) {
126
+ const bytes = new Uint8Array(buffer);
127
+ let binary = "";
128
+ for (let i = 0; i < bytes.length; i++) {
129
+ binary += String.fromCharCode(bytes[i]);
130
+ }
131
+ let base64 = btoa(binary);
132
+ base64 = base64.replace(/\+/g, "-").replace(/\//g, "_");
133
+ if (!padding) {
134
+ base64 = base64.replace(/=+$/, "");
135
+ }
136
+ return base64;
137
+ }
138
+ function generateSecureRandomBase64Url(byteLength) {
139
+ const bytes = new Uint8Array(byteLength);
140
+ crypto.getRandomValues(bytes);
141
+ const buffer = bytes.buffer;
142
+ return arrayBufferToBase64Url(buffer, false);
143
+ }
144
+ function notBlankStringOrNull(value) {
145
+ if (value?.trim()) {
146
+ return value;
147
+ } else {
148
+ return null;
149
+ }
150
+ }
151
+
152
+ // src/shared/pkce.ts
153
+ async function generatePKCEParams(config) {
154
+ const state = generateSecureRandomBase64Url(32);
155
+ const codeVerifier = generateSecureRandomBase64Url(32);
156
+ const codeChallengeRaw = await sha256(codeVerifier);
157
+ const codeChallenge = arrayBufferToBase64Url(codeChallengeRaw, false);
158
+ return {
159
+ clientId: config.clientId,
160
+ responseType: "code",
161
+ redirectUri: config.redirectUri,
162
+ codeChallenge: CodeChallenge(codeChallenge),
163
+ codeChallengeMethod: "S256",
164
+ state: PKCEState(state),
165
+ codeVerifier: CodeVerifier(codeVerifier)
166
+ };
167
+ }
168
+
105
169
  // src/shared/types/asset.ts
106
170
  var AssetId = (value) => value;
107
171
  var AssetCode = (value) => value;
@@ -128,14 +192,6 @@ var NotAuthenticatedError = class extends Error {
128
192
  }
129
193
  };
130
194
 
131
- // src/shared/types/oauth.ts
132
- var AuthorizationCode = (value) => value;
133
- var CodeVerifier = (value) => value;
134
- var CodeChallenge = (value) => value;
135
- var RedirectUri = (value) => value;
136
- var ClientId = (value) => value;
137
- var ClientSecret = (value) => value;
138
-
139
195
  // src/shared/types/oauth-session.ts
140
196
  var OAuthRefreshToken = (value) => value;
141
197
  var OAuthSession = {
@@ -151,15 +207,6 @@ var OAuthSession = {
151
207
  }
152
208
  };
153
209
 
154
- // src/shared/utils.ts
155
- function notBlankStringOrNull(value) {
156
- if (value?.trim()) {
157
- return value;
158
- } else {
159
- return null;
160
- }
161
- }
162
-
163
210
  // src/shared/types/offer.ts
164
211
  var OfferId = (value) => value;
165
212
  var OfferSlug = (value) => value;
@@ -356,6 +403,7 @@ var User = {
356
403
  OfferOptionId,
357
404
  OfferOptionSlug,
358
405
  OfferSlug,
406
+ PKCEState,
359
407
  PaginatedResponse,
360
408
  PaginationParams,
361
409
  Participation,
@@ -370,6 +418,7 @@ var User = {
370
418
  UserEmail,
371
419
  UserId,
372
420
  WalletAddress,
373
- fetchAllPages
421
+ fetchAllPages,
422
+ generatePKCEParams
374
423
  });
375
424
  //# sourceMappingURL=index.cjs.map