@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.
- package/dist/{chunk-N3WBC2VS.js → chunk-5C4TEVM7.js} +2 -2
- package/dist/{chunk-V6WO67RO.js → chunk-7SB2GKEU.js} +1 -1
- package/dist/{chunk-V6WO67RO.js.map → chunk-7SB2GKEU.js.map} +1 -1
- package/dist/chunk-UEJVCU2J.js +43 -0
- package/dist/chunk-UEJVCU2J.js.map +1 -0
- package/dist/client/index.cjs +441 -302
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +33 -3
- package/dist/client/index.d.ts +33 -3
- package/dist/client/index.js +422 -308
- package/dist/client/index.js.map +1 -1
- package/dist/{requirement-BEO42QOr.d.ts → requirement-Dk6nYN1c.d.cts} +8 -6
- package/dist/{requirement-BEO42QOr.d.cts → requirement-Dk6nYN1c.d.ts} +8 -6
- package/dist/server/index.cjs +41 -9
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +68 -8
- package/dist/server/index.d.ts +68 -8
- package/dist/server/index.js +42 -11
- package/dist/server/index.js.map +1 -1
- package/dist/shared/index.cjs +68 -19
- package/dist/shared/index.cjs.map +1 -1
- package/dist/shared/index.d.cts +23 -3
- package/dist/shared/index.d.ts +23 -3
- package/dist/shared/index.js +8 -4
- package/dist/shared/index.js.map +1 -1
- package/package.json +27 -28
- package/dist/chunk-CRACFEJ4.js +0 -17
- package/dist/chunk-CRACFEJ4.js.map +0 -1
- package/dist/client/styles.css +0 -2
- /package/dist/{chunk-N3WBC2VS.js.map → chunk-5C4TEVM7.js.map} +0 -0
package/dist/server/index.d.cts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
26
|
-
*
|
|
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
|
-
|
|
146
|
+
declare class WritableSessionStoreRequiredError extends Error {
|
|
147
|
+
constructor(message?: string);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { type CoinListServer, type ServerConfig, type SessionStore, WritableSessionStoreRequiredError, createCoinListServer };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
26
|
-
*
|
|
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
|
-
|
|
146
|
+
declare class WritableSessionStoreRequiredError extends Error {
|
|
147
|
+
constructor(message?: string);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { type CoinListServer, type ServerConfig, type SessionStore, WritableSessionStoreRequiredError, createCoinListServer };
|
package/dist/server/index.js
CHANGED
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
fetchParticipations,
|
|
13
13
|
fetchParticipationsPage,
|
|
14
14
|
fetchRequirementStatuses
|
|
15
|
-
} from "../chunk-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
await setSession(session);
|
|
64
81
|
return session;
|
|
65
82
|
}
|
|
66
83
|
async accessToken() {
|
|
67
|
-
const
|
|
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
|
|
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
|
|
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
|
|
117
|
+
await setSession(newSession);
|
|
93
118
|
return newSession.accessToken;
|
|
94
119
|
} catch {
|
|
95
|
-
await
|
|
120
|
+
await setSession(null);
|
|
96
121
|
return null;
|
|
97
122
|
}
|
|
98
123
|
}
|
|
99
124
|
async logout() {
|
|
100
|
-
const
|
|
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
|
|
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
|
package/dist/server/index.js.map
CHANGED
|
@@ -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":[]}
|
package/dist/shared/index.cjs
CHANGED
|
@@ -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
|