@carlonicora/nextjs-jsonapi 6.22.0 → 6.23.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/{BlockNoteEditor-HW3TK7SM.js → BlockNoteEditor-PPRML5CO.js} +23 -23
- package/dist/{BlockNoteEditor-HW3TK7SM.js.map → BlockNoteEditor-PPRML5CO.js.map} +1 -1
- package/dist/{BlockNoteEditor-2XSIQCEB.mjs → BlockNoteEditor-UO4ZRCUM.mjs} +8 -8
- package/dist/{BlockNoteViewer-2UDFTXRS.mjs → BlockNoteViewer-2TJZ6JET.mjs} +6 -6
- package/dist/{BlockNoteViewer-4OHBRE4Q.js → BlockNoteViewer-K2HBF3IP.js} +7 -7
- package/dist/{BlockNoteViewer-4OHBRE4Q.js.map → BlockNoteViewer-K2HBF3IP.js.map} +1 -1
- package/dist/{JsonApiRequest-IKKHWCPB.mjs → JsonApiRequest-ADLBJ5RX.mjs} +2 -2
- package/dist/JsonApiRequest-KC6DOHKT.js +26 -0
- package/dist/{JsonApiRequest-G4J2WTSU.js.map → JsonApiRequest-KC6DOHKT.js.map} +1 -1
- package/dist/billing/index.js +495 -495
- package/dist/billing/index.mjs +7 -7
- package/dist/{chunk-N5ZE2Z6I.js → chunk-53SD4YRF.js} +5 -5
- package/dist/{chunk-N5ZE2Z6I.js.map → chunk-53SD4YRF.js.map} +1 -1
- package/dist/{chunk-4RA5PRXX.mjs → chunk-7OIWOPM5.mjs} +2 -2
- package/dist/{chunk-WCEYT3GG.js → chunk-A5M3MORY.js} +6 -3
- package/dist/chunk-A5M3MORY.js.map +1 -0
- package/dist/{chunk-AUD6ZLBB.mjs → chunk-BE6UMM5X.mjs} +13 -8
- package/dist/chunk-BE6UMM5X.mjs.map +1 -0
- package/dist/{chunk-KV4N7DEG.js → chunk-BQ2WAG4K.js} +29 -24
- package/dist/chunk-BQ2WAG4K.js.map +1 -0
- package/dist/{chunk-OQQ7CZWT.js → chunk-GMMDXINC.js} +12 -12
- package/dist/{chunk-OQQ7CZWT.js.map → chunk-GMMDXINC.js.map} +1 -1
- package/dist/{chunk-ZB6OYIMB.js → chunk-HN7L6RRW.js} +1296 -1093
- package/dist/{chunk-DPXOPLQP.mjs.map → chunk-HN7L6RRW.js.map} +1 -1
- package/dist/{chunk-EFQIMPCJ.mjs → chunk-HP6DT6K3.mjs} +10 -10
- package/dist/{chunk-JSHLBRFV.mjs → chunk-N2BFT7KI.mjs} +2 -2
- package/dist/chunk-N2BFT7KI.mjs.map +1 -0
- package/dist/{chunk-YWLSIIE6.js → chunk-P7L6OFX5.js} +11 -11
- package/dist/{chunk-YWLSIIE6.js.map → chunk-P7L6OFX5.js.map} +1 -1
- package/dist/{chunk-FXRRQ7Q6.js → chunk-R5722NLL.js} +919 -919
- package/dist/{chunk-FXRRQ7Q6.js.map → chunk-R5722NLL.js.map} +1 -1
- package/dist/{chunk-GM4VZTWL.js → chunk-RCOKQ3R4.js} +14 -8
- package/dist/chunk-RCOKQ3R4.js.map +1 -0
- package/dist/{chunk-M3N7LPHX.mjs → chunk-S7SPAC6X.mjs} +6 -3
- package/dist/chunk-S7SPAC6X.mjs.map +1 -0
- package/dist/{chunk-54K2CZ2F.js → chunk-SO3YUCL6.js} +4 -4
- package/dist/{chunk-54K2CZ2F.js.map → chunk-SO3YUCL6.js.map} +1 -1
- package/dist/{chunk-DPXOPLQP.mjs → chunk-T6G2F2BK.mjs} +220 -17
- package/dist/chunk-T6G2F2BK.mjs.map +1 -0
- package/dist/{chunk-G2UIQSNJ.mjs → chunk-TMV7TL7O.mjs} +4 -4
- package/dist/{chunk-BD6ARGGI.js → chunk-TNTCLVUR.js} +15 -15
- package/dist/{chunk-BD6ARGGI.js.map → chunk-TNTCLVUR.js.map} +1 -1
- package/dist/{chunk-QIEF7HIL.mjs → chunk-VNUT2D27.mjs} +2 -2
- package/dist/{chunk-KTGKW72G.mjs → chunk-W4ML6D2X.mjs} +2 -2
- package/dist/{chunk-MQ7TXALH.mjs → chunk-YMCL3JXZ.mjs} +14 -8
- package/dist/chunk-YMCL3JXZ.mjs.map +1 -0
- package/dist/{chunk-QGVVF77L.js → chunk-YSE2Z4RB.js} +11 -11
- package/dist/{chunk-QGVVF77L.js.map → chunk-YSE2Z4RB.js.map} +1 -1
- package/dist/{chunk-A3RB47X4.mjs → chunk-YTDGYI62.mjs} +3 -3
- package/dist/client/JsonApiClient.d.ts +5 -0
- package/dist/client/JsonApiClient.d.ts.map +1 -1
- package/dist/client/index.js +8 -8
- package/dist/client/index.mjs +7 -7
- package/dist/client/request.d.ts +5 -0
- package/dist/client/request.d.ts.map +1 -1
- package/dist/components/index.js +8 -8
- package/dist/components/index.mjs +7 -7
- package/dist/contexts/index.js +8 -8
- package/dist/contexts/index.mjs +7 -7
- package/dist/core/abstracts/AbstractService.d.ts +5 -0
- package/dist/core/abstracts/AbstractService.d.ts.map +1 -1
- package/dist/core/abstracts/ClientAbstractService.d.ts +5 -0
- package/dist/core/abstracts/ClientAbstractService.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +24 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +23 -5
- package/dist/core/registry/ModuleRegistry.d.ts +3 -0
- package/dist/core/registry/ModuleRegistry.d.ts.map +1 -1
- package/dist/email-automations/index.js +121 -121
- package/dist/email-automations/index.mjs +7 -7
- package/dist/features/analytics/index.js +139 -139
- package/dist/features/analytics/index.mjs +7 -7
- package/dist/features/analytics/server.js +9 -9
- package/dist/features/analytics/server.mjs +7 -7
- package/dist/features/blog/index.js +154 -154
- package/dist/features/blog/index.mjs +7 -7
- package/dist/features/blog/server.js +27 -27
- package/dist/features/blog/server.mjs +6 -6
- package/dist/features/discord-connection/data/discord-connect-confirmation.d.ts +11 -0
- package/dist/features/discord-connection/data/discord-connect-confirmation.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connect-confirmation.interface.d.ts +14 -0
- package/dist/features/discord-connection/data/discord-connect-confirmation.interface.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connect-confirmation.service.d.ts +10 -0
- package/dist/features/discord-connection/data/discord-connect-confirmation.service.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connect-intent.d.ts +11 -0
- package/dist/features/discord-connection/data/discord-connect-intent.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connect-intent.interface.d.ts +15 -0
- package/dist/features/discord-connection/data/discord-connect-intent.interface.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connect-intent.service.d.ts +11 -0
- package/dist/features/discord-connection/data/discord-connect-intent.service.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connection.d.ts +14 -0
- package/dist/features/discord-connection/data/discord-connection.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connection.interface.d.ts +10 -0
- package/dist/features/discord-connection/data/discord-connection.interface.d.ts.map +1 -0
- package/dist/features/discord-connection/data/discord-connection.service.d.ts +12 -0
- package/dist/features/discord-connection/data/discord-connection.service.d.ts.map +1 -0
- package/dist/features/discord-connection/data/index.d.ts +10 -0
- package/dist/features/discord-connection/data/index.d.ts.map +1 -0
- package/dist/features/discord-connection/discord-connection.module.d.ts +5 -0
- package/dist/features/discord-connection/discord-connection.module.d.ts.map +1 -0
- package/dist/features/discord-connection/index.d.ts +3 -0
- package/dist/features/discord-connection/index.d.ts.map +1 -0
- package/dist/features/help/index.js +41 -41
- package/dist/features/help/index.mjs +7 -7
- package/dist/features/tokenusage/index.js +130 -130
- package/dist/features/tokenusage/index.mjs +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +26 -6
- package/dist/local-transport-MJPOKPTU.js +15 -0
- package/dist/{local-transport-4YDJ6FQG.js.map → local-transport-MJPOKPTU.js.map} +1 -1
- package/dist/{local-transport-C7YTA4I7.mjs → local-transport-RVD4FZGA.mjs} +5 -5
- package/dist/offline/index.js +51 -51
- package/dist/offline/index.mjs +6 -6
- package/dist/{request-66FGJIHB.mjs → request-4AWGPIGH.mjs} +2 -2
- package/dist/{request-6W6UGKJV.js → request-H5FZBZA2.js} +3 -3
- package/dist/{request-6W6UGKJV.js.map → request-H5FZBZA2.js.map} +1 -1
- package/dist/server/index.js +8 -8
- package/dist/server/index.mjs +7 -7
- package/dist/unified/JsonApiRequest.d.ts +5 -0
- package/dist/unified/JsonApiRequest.d.ts.map +1 -1
- package/dist/write-through-7I3LA2WN.mjs +15 -0
- package/dist/write-through-HE2QSOVI.js +15 -0
- package/dist/{write-through-BAPS24GL.js.map → write-through-HE2QSOVI.js.map} +1 -1
- package/package.json +1 -1
- package/src/client/JsonApiClient.ts +10 -0
- package/src/client/__tests__/request.credentials.spec.ts +103 -0
- package/src/client/request.ts +9 -0
- package/src/core/abstracts/AbstractService.ts +10 -0
- package/src/core/abstracts/ClientAbstractService.ts +10 -0
- package/src/core/index.ts +3 -0
- package/src/core/registry/ModuleRegistry.ts +4 -0
- package/src/features/discord-connection/data/__tests__/DiscordConnection.spec.ts +114 -0
- package/src/features/discord-connection/data/discord-connect-confirmation.interface.ts +16 -0
- package/src/features/discord-connection/data/discord-connect-confirmation.service.ts +21 -0
- package/src/features/discord-connection/data/discord-connect-confirmation.ts +46 -0
- package/src/features/discord-connection/data/discord-connect-intent.interface.ts +16 -0
- package/src/features/discord-connection/data/discord-connect-intent.service.ts +20 -0
- package/src/features/discord-connection/data/discord-connect-intent.ts +42 -0
- package/src/features/discord-connection/data/discord-connection.interface.ts +10 -0
- package/src/features/discord-connection/data/discord-connection.service.ts +17 -0
- package/src/features/discord-connection/data/discord-connection.ts +41 -0
- package/src/features/discord-connection/data/index.ts +9 -0
- package/src/features/discord-connection/discord-connection.module.ts +28 -0
- package/src/features/discord-connection/index.ts +2 -0
- package/src/index.ts +1 -1
- package/src/unified/JsonApiRequest.ts +14 -0
- package/dist/JsonApiRequest-G4J2WTSU.js +0 -26
- package/dist/chunk-AUD6ZLBB.mjs.map +0 -1
- package/dist/chunk-GM4VZTWL.js.map +0 -1
- package/dist/chunk-JSHLBRFV.mjs.map +0 -1
- package/dist/chunk-KV4N7DEG.js.map +0 -1
- package/dist/chunk-M3N7LPHX.mjs.map +0 -1
- package/dist/chunk-MQ7TXALH.mjs.map +0 -1
- package/dist/chunk-WCEYT3GG.js.map +0 -1
- package/dist/chunk-ZB6OYIMB.js.map +0 -1
- package/dist/local-transport-4YDJ6FQG.js +0 -15
- package/dist/write-through-BAPS24GL.js +0 -15
- package/dist/write-through-IWQKXYHE.mjs +0 -15
- /package/dist/{BlockNoteEditor-2XSIQCEB.mjs.map → BlockNoteEditor-UO4ZRCUM.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-2UDFTXRS.mjs.map → BlockNoteViewer-2TJZ6JET.mjs.map} +0 -0
- /package/dist/{JsonApiRequest-IKKHWCPB.mjs.map → JsonApiRequest-ADLBJ5RX.mjs.map} +0 -0
- /package/dist/{chunk-4RA5PRXX.mjs.map → chunk-7OIWOPM5.mjs.map} +0 -0
- /package/dist/{chunk-EFQIMPCJ.mjs.map → chunk-HP6DT6K3.mjs.map} +0 -0
- /package/dist/{chunk-G2UIQSNJ.mjs.map → chunk-TMV7TL7O.mjs.map} +0 -0
- /package/dist/{chunk-QIEF7HIL.mjs.map → chunk-VNUT2D27.mjs.map} +0 -0
- /package/dist/{chunk-KTGKW72G.mjs.map → chunk-W4ML6D2X.mjs.map} +0 -0
- /package/dist/{chunk-A3RB47X4.mjs.map → chunk-YTDGYI62.mjs.map} +0 -0
- /package/dist/{local-transport-C7YTA4I7.mjs.map → local-transport-RVD4FZGA.mjs.map} +0 -0
- /package/dist/{request-66FGJIHB.mjs.map → request-4AWGPIGH.mjs.map} +0 -0
- /package/dist/{write-through-IWQKXYHE.mjs.map → write-through-7I3LA2WN.mjs.map} +0 -0
|
@@ -93,6 +93,7 @@ export async function ClientJsonApiGet(params: {
|
|
|
93
93
|
companyId?: string;
|
|
94
94
|
language: string;
|
|
95
95
|
baseUrl?: string;
|
|
96
|
+
credentials?: RequestCredentials;
|
|
96
97
|
}): Promise<ApiResponseInterface> {
|
|
97
98
|
runClientBootstrapper();
|
|
98
99
|
const token = await getClientToken();
|
|
@@ -104,6 +105,7 @@ export async function ClientJsonApiGet(params: {
|
|
|
104
105
|
companyId: params.companyId,
|
|
105
106
|
language: params.language,
|
|
106
107
|
additionalHeaders: _clientConfig?.additionalHeaders,
|
|
108
|
+
credentials: params.credentials,
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
return translateResponse({
|
|
@@ -132,6 +134,7 @@ export async function ClientJsonApiPost(params: {
|
|
|
132
134
|
language: string;
|
|
133
135
|
responseType?: ApiRequestDataTypeInterface;
|
|
134
136
|
baseUrl?: string;
|
|
137
|
+
credentials?: RequestCredentials;
|
|
135
138
|
}): Promise<ApiResponseInterface> {
|
|
136
139
|
runClientBootstrapper();
|
|
137
140
|
const token = await getClientToken();
|
|
@@ -152,6 +155,7 @@ export async function ClientJsonApiPost(params: {
|
|
|
152
155
|
companyId: params.companyId,
|
|
153
156
|
language: params.language,
|
|
154
157
|
additionalHeaders: _clientConfig?.additionalHeaders,
|
|
158
|
+
credentials: params.credentials,
|
|
155
159
|
});
|
|
156
160
|
|
|
157
161
|
return translateResponse({
|
|
@@ -171,6 +175,7 @@ export async function ClientJsonApiPut(params: {
|
|
|
171
175
|
language: string;
|
|
172
176
|
responseType?: ApiRequestDataTypeInterface;
|
|
173
177
|
baseUrl?: string;
|
|
178
|
+
credentials?: RequestCredentials;
|
|
174
179
|
}): Promise<ApiResponseInterface> {
|
|
175
180
|
runClientBootstrapper();
|
|
176
181
|
const token = await getClientToken();
|
|
@@ -191,6 +196,7 @@ export async function ClientJsonApiPut(params: {
|
|
|
191
196
|
companyId: params.companyId,
|
|
192
197
|
language: params.language,
|
|
193
198
|
additionalHeaders: _clientConfig?.additionalHeaders,
|
|
199
|
+
credentials: params.credentials,
|
|
194
200
|
});
|
|
195
201
|
|
|
196
202
|
return translateResponse({
|
|
@@ -211,6 +217,7 @@ export async function ClientJsonApiPatch(params: {
|
|
|
211
217
|
responseType?: ApiRequestDataTypeInterface;
|
|
212
218
|
language: string;
|
|
213
219
|
baseUrl?: string;
|
|
220
|
+
credentials?: RequestCredentials;
|
|
214
221
|
}): Promise<ApiResponseInterface> {
|
|
215
222
|
runClientBootstrapper();
|
|
216
223
|
const token = await getClientToken();
|
|
@@ -231,6 +238,7 @@ export async function ClientJsonApiPatch(params: {
|
|
|
231
238
|
companyId: params.companyId,
|
|
232
239
|
language: params.language,
|
|
233
240
|
additionalHeaders: _clientConfig?.additionalHeaders,
|
|
241
|
+
credentials: params.credentials,
|
|
234
242
|
});
|
|
235
243
|
|
|
236
244
|
return translateResponse({
|
|
@@ -248,6 +256,7 @@ export async function ClientJsonApiDelete(params: {
|
|
|
248
256
|
language: string;
|
|
249
257
|
responseType?: ApiRequestDataTypeInterface;
|
|
250
258
|
baseUrl?: string;
|
|
259
|
+
credentials?: RequestCredentials;
|
|
251
260
|
}): Promise<ApiResponseInterface> {
|
|
252
261
|
runClientBootstrapper();
|
|
253
262
|
const token = await getClientToken();
|
|
@@ -259,6 +268,7 @@ export async function ClientJsonApiDelete(params: {
|
|
|
259
268
|
companyId: params.companyId,
|
|
260
269
|
language: params.language,
|
|
261
270
|
additionalHeaders: _clientConfig?.additionalHeaders,
|
|
271
|
+
credentials: params.credentials,
|
|
262
272
|
});
|
|
263
273
|
|
|
264
274
|
return translateResponse({
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { AbstractService, HttpMethod } from "../../core/abstracts/AbstractService";
|
|
3
|
+
import { ClientAbstractService, ClientHttpMethod } from "../../core/abstracts/ClientAbstractService";
|
|
4
|
+
import { ApiRequestDataTypeInterface } from "../../core/interfaces/ApiRequestDataTypeInterface";
|
|
5
|
+
import { ModuleRegistry } from "../../core/registry/ModuleRegistry";
|
|
6
|
+
import { DiscordConnectIntent } from "../../features/discord-connection/data/discord-connect-intent";
|
|
7
|
+
import { directFetch } from "../request";
|
|
8
|
+
|
|
9
|
+
const TYPE = { name: "tests" } as any;
|
|
10
|
+
const ENDPOINT = "http://api.test/tests";
|
|
11
|
+
// Built by a model method, as references/frontend/03-services.md allows with overridesJsonApiCreation.
|
|
12
|
+
const body = () => new DiscordConnectIntent().createJsonApi({ id: "t-1", returnPath: "/" });
|
|
13
|
+
|
|
14
|
+
// createJsonApi() resolves Modules.X through the real ModuleRegistry,
|
|
15
|
+
// which is shared across test files in a worker: register only when absent.
|
|
16
|
+
const registerIfAbsent = (key: string, module: ApiRequestDataTypeInterface) => {
|
|
17
|
+
try {
|
|
18
|
+
ModuleRegistry.get(key as any);
|
|
19
|
+
} catch {
|
|
20
|
+
ModuleRegistry.register(key, module);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
registerIfAbsent("DiscordConnectIntent", { name: "discord-connect-intents", model: DiscordConnectIntent } as any);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
class TestService extends AbstractService {
|
|
29
|
+
static post(credentials?: RequestCredentials) {
|
|
30
|
+
return this.callApi({
|
|
31
|
+
type: TYPE,
|
|
32
|
+
method: HttpMethod.POST,
|
|
33
|
+
endpoint: ENDPOINT,
|
|
34
|
+
input: body(),
|
|
35
|
+
overridesJsonApiCreation: true,
|
|
36
|
+
suppressGlobalError: true,
|
|
37
|
+
...(credentials ? { credentials } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class TestClientService extends ClientAbstractService {
|
|
43
|
+
static post(credentials?: RequestCredentials) {
|
|
44
|
+
return this.callApi({
|
|
45
|
+
type: TYPE,
|
|
46
|
+
method: ClientHttpMethod.POST,
|
|
47
|
+
endpoint: ENDPOINT,
|
|
48
|
+
input: body(),
|
|
49
|
+
overridesJsonApiCreation: true,
|
|
50
|
+
...(credentials ? { credentials } : {}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const fetchOptions = (fetchMock: ReturnType<typeof vi.fn>): RequestInit => fetchMock.mock.calls[0][1] as RequestInit;
|
|
56
|
+
|
|
57
|
+
describe("callApi credentials", () => {
|
|
58
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
// A 500 keeps the response path out of the test: only the fetch options matter.
|
|
62
|
+
fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({}), { status: 500 }));
|
|
63
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.unstubAllGlobals();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("callApi passes credentials to fetch", async () => {
|
|
71
|
+
await TestService.post("include").catch(() => undefined);
|
|
72
|
+
|
|
73
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
74
|
+
expect(fetchOptions(fetchMock).credentials).toBe("include");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("callApi without credentials leaves fetch options unchanged", async () => {
|
|
78
|
+
await TestService.post().catch(() => undefined);
|
|
79
|
+
|
|
80
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
81
|
+
expect(Object.keys(fetchOptions(fetchMock)).sort()).toEqual(["body", "headers", "method"]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("ClientAbstractService callApi passes credentials to fetch", async () => {
|
|
85
|
+
await TestClientService.post("include").catch(() => undefined);
|
|
86
|
+
|
|
87
|
+
expect(fetchOptions(fetchMock).credentials).toBe("include");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("ClientAbstractService callApi without credentials leaves fetch options unchanged", async () => {
|
|
91
|
+
await TestClientService.post().catch(() => undefined);
|
|
92
|
+
|
|
93
|
+
expect(Object.keys(fetchOptions(fetchMock)).sort()).toEqual(["body", "headers", "method"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("directFetch sets credentials only when given", async () => {
|
|
97
|
+
await directFetch({ method: "GET", url: ENDPOINT, language: "en", credentials: "include" });
|
|
98
|
+
await directFetch({ method: "GET", url: ENDPOINT, language: "en" });
|
|
99
|
+
|
|
100
|
+
expect((fetchMock.mock.calls[0][1] as RequestInit).credentials).toBe("include");
|
|
101
|
+
expect("credentials" in (fetchMock.mock.calls[1][1] as RequestInit)).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/client/request.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface DirectFetchParams {
|
|
|
14
14
|
companyId?: string;
|
|
15
15
|
language: string;
|
|
16
16
|
additionalHeaders?: Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Optional `fetch` credentials mode (e.g. "include" so the browser stores a cookie the API sets).
|
|
19
|
+
* Omitted => no `credentials` option is passed, unchanged behaviour.
|
|
20
|
+
*/
|
|
21
|
+
credentials?: RequestCredentials;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
/**
|
|
@@ -108,6 +113,10 @@ async function fetchFromApi(params: DirectFetchParams): Promise<ApiData> {
|
|
|
108
113
|
options.body = requestBody;
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
if (params.credentials) {
|
|
117
|
+
options.credentials = params.credentials;
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
if (params.token) {
|
|
112
121
|
options.headers = { ...options.headers, Authorization: `Bearer ${params.token}` };
|
|
113
122
|
}
|
|
@@ -143,6 +143,11 @@ export abstract class AbstractService {
|
|
|
143
143
|
* fully backward compatible. Ignored when `endpoint` starts with "http" (passthrough).
|
|
144
144
|
*/
|
|
145
145
|
baseUrl?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Optional browser `fetch` credentials mode, e.g. "include" so the browser stores a
|
|
148
|
+
* cookie the API sets. Omitted => no `credentials` option is passed (unchanged).
|
|
149
|
+
*/
|
|
150
|
+
credentials?: RequestCredentials;
|
|
146
151
|
}): Promise<T> {
|
|
147
152
|
// Dynamic import to avoid bundling issues
|
|
148
153
|
const { JsonApiGet, JsonApiPost, JsonApiPut, JsonApiPatch, JsonApiDelete } =
|
|
@@ -168,6 +173,7 @@ export abstract class AbstractService {
|
|
|
168
173
|
language: language,
|
|
169
174
|
token: params.token,
|
|
170
175
|
baseUrl: params.baseUrl,
|
|
176
|
+
credentials: params.credentials,
|
|
171
177
|
});
|
|
172
178
|
break;
|
|
173
179
|
case HttpMethod.POST:
|
|
@@ -182,6 +188,7 @@ export abstract class AbstractService {
|
|
|
182
188
|
files: params.files,
|
|
183
189
|
token: params.token,
|
|
184
190
|
baseUrl: params.baseUrl,
|
|
191
|
+
credentials: params.credentials,
|
|
185
192
|
});
|
|
186
193
|
break;
|
|
187
194
|
case HttpMethod.PUT:
|
|
@@ -195,6 +202,7 @@ export abstract class AbstractService {
|
|
|
195
202
|
files: params.files,
|
|
196
203
|
token: params.token,
|
|
197
204
|
baseUrl: params.baseUrl,
|
|
205
|
+
credentials: params.credentials,
|
|
198
206
|
});
|
|
199
207
|
break;
|
|
200
208
|
case HttpMethod.PATCH:
|
|
@@ -209,6 +217,7 @@ export abstract class AbstractService {
|
|
|
209
217
|
files: params.files,
|
|
210
218
|
token: params.token,
|
|
211
219
|
baseUrl: params.baseUrl,
|
|
220
|
+
credentials: params.credentials,
|
|
212
221
|
});
|
|
213
222
|
break;
|
|
214
223
|
case HttpMethod.DELETE:
|
|
@@ -220,6 +229,7 @@ export abstract class AbstractService {
|
|
|
220
229
|
responseType: params.responseType,
|
|
221
230
|
token: params.token,
|
|
222
231
|
baseUrl: params.baseUrl,
|
|
232
|
+
credentials: params.credentials,
|
|
223
233
|
});
|
|
224
234
|
break;
|
|
225
235
|
default:
|
|
@@ -148,6 +148,11 @@ export abstract class ClientAbstractService {
|
|
|
148
148
|
* Omitted => existing global `NEXT_PUBLIC_API_URL` resolution, unchanged.
|
|
149
149
|
*/
|
|
150
150
|
baseUrl?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Optional browser `fetch` credentials mode, e.g. "include" so the browser stores a
|
|
153
|
+
* cookie the API sets. Omitted => no `credentials` option is passed (unchanged).
|
|
154
|
+
*/
|
|
155
|
+
credentials?: RequestCredentials;
|
|
151
156
|
}): Promise<T> {
|
|
152
157
|
let apiResponse: ApiResponseInterface;
|
|
153
158
|
|
|
@@ -162,6 +167,7 @@ export abstract class ClientAbstractService {
|
|
|
162
167
|
companyId: params.companyId,
|
|
163
168
|
language: language,
|
|
164
169
|
baseUrl: params.baseUrl,
|
|
170
|
+
credentials: params.credentials,
|
|
165
171
|
});
|
|
166
172
|
break;
|
|
167
173
|
case ClientHttpMethod.POST:
|
|
@@ -175,6 +181,7 @@ export abstract class ClientAbstractService {
|
|
|
175
181
|
responseType: params.responseType,
|
|
176
182
|
files: params.files,
|
|
177
183
|
baseUrl: params.baseUrl,
|
|
184
|
+
credentials: params.credentials,
|
|
178
185
|
});
|
|
179
186
|
break;
|
|
180
187
|
case ClientHttpMethod.PUT:
|
|
@@ -187,6 +194,7 @@ export abstract class ClientAbstractService {
|
|
|
187
194
|
responseType: params.responseType,
|
|
188
195
|
files: params.files,
|
|
189
196
|
baseUrl: params.baseUrl,
|
|
197
|
+
credentials: params.credentials,
|
|
190
198
|
});
|
|
191
199
|
break;
|
|
192
200
|
case ClientHttpMethod.PATCH:
|
|
@@ -200,6 +208,7 @@ export abstract class ClientAbstractService {
|
|
|
200
208
|
responseType: params.responseType,
|
|
201
209
|
files: params.files,
|
|
202
210
|
baseUrl: params.baseUrl,
|
|
211
|
+
credentials: params.credentials,
|
|
203
212
|
});
|
|
204
213
|
break;
|
|
205
214
|
case ClientHttpMethod.DELETE:
|
|
@@ -210,6 +219,7 @@ export abstract class ClientAbstractService {
|
|
|
210
219
|
language: language,
|
|
211
220
|
responseType: params.responseType,
|
|
212
221
|
baseUrl: params.baseUrl,
|
|
222
|
+
credentials: params.credentials,
|
|
213
223
|
});
|
|
214
224
|
break;
|
|
215
225
|
default:
|
package/src/core/index.ts
CHANGED
|
@@ -35,6 +35,9 @@ export * from "../permissions";
|
|
|
35
35
|
export * from "../features/account-deletion/account-deletion.module";
|
|
36
36
|
export * from "../features/account-deletion/data";
|
|
37
37
|
export * from "../features/account-deletion/utils";
|
|
38
|
+
// Discord connect (opt-in): data and modules. No components ship from the library.
|
|
39
|
+
export * from "../features/discord-connection/discord-connection.module";
|
|
40
|
+
export * from "../features/discord-connection/data";
|
|
38
41
|
export * from "../features/auth/auth.module";
|
|
39
42
|
export * from "../features/auth/totp-authenticator.module";
|
|
40
43
|
export * from "../features/auth/totp-setup.module";
|
|
@@ -109,6 +109,10 @@ export interface FoundationModuleDefinitions {
|
|
|
109
109
|
AnalyticsPageView: ModuleWithPermissions;
|
|
110
110
|
// Account deletion (opt-in: registered only by apps that enable it)
|
|
111
111
|
AccountDeletion: ModuleWithPermissions;
|
|
112
|
+
// Discord connect (registered by apps that offer it)
|
|
113
|
+
DiscordConnection: ModuleWithPermissions;
|
|
114
|
+
DiscordConnectIntent: ModuleWithPermissions;
|
|
115
|
+
DiscordConnectConfirmation: ModuleWithPermissions;
|
|
112
116
|
// Offline mode (opt-in: registered by apps that spread offlineModules)
|
|
113
117
|
OfflineChangeSet: ModuleWithPermissions;
|
|
114
118
|
RootSync: ModuleWithPermissions;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import { ApiRequestDataTypeInterface } from "../../../../core/interfaces/ApiRequestDataTypeInterface";
|
|
3
|
+
import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
|
|
4
|
+
import { DiscordConnectConfirmation } from "../discord-connect-confirmation";
|
|
5
|
+
import { DiscordConnectIntent } from "../discord-connect-intent";
|
|
6
|
+
import { DiscordConnection } from "../discord-connection";
|
|
7
|
+
|
|
8
|
+
// createJsonApi() resolves Modules.X through the real ModuleRegistry,
|
|
9
|
+
// which is shared across test files in a worker: register only when absent.
|
|
10
|
+
const registerIfAbsent = (key: string, module: ApiRequestDataTypeInterface) => {
|
|
11
|
+
try {
|
|
12
|
+
ModuleRegistry.get(key as any);
|
|
13
|
+
} catch {
|
|
14
|
+
ModuleRegistry.register(key, module);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
registerIfAbsent("DiscordConnection", { name: "discord-connections", model: DiscordConnection } as any);
|
|
20
|
+
registerIfAbsent("DiscordConnectIntent", { name: "discord-connect-intents", model: DiscordConnectIntent } as any);
|
|
21
|
+
registerIfAbsent("DiscordConnectConfirmation", {
|
|
22
|
+
name: "discord-connect-confirmations",
|
|
23
|
+
model: DiscordConnectConfirmation,
|
|
24
|
+
} as any);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("DiscordConnection", () => {
|
|
28
|
+
it("rehydrates connected and discordName", () => {
|
|
29
|
+
const model = new DiscordConnection().rehydrate({
|
|
30
|
+
jsonApi: { type: "discord-connections", id: "u-1", attributes: { connected: true, discordName: "carlo" } },
|
|
31
|
+
included: [],
|
|
32
|
+
} as any);
|
|
33
|
+
|
|
34
|
+
expect(model.id).toBe("u-1");
|
|
35
|
+
expect(model.connected).toBe(true);
|
|
36
|
+
expect(model.discordName).toBe("carlo");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("defaults connected to false and discordName to undefined", () => {
|
|
40
|
+
const model = new DiscordConnection().rehydrate({
|
|
41
|
+
jsonApi: { type: "discord-connections", id: "u-1", attributes: {} },
|
|
42
|
+
included: [],
|
|
43
|
+
} as any);
|
|
44
|
+
|
|
45
|
+
expect(model.connected).toBe(false);
|
|
46
|
+
expect(model.discordName).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("DiscordConnection createJsonApi has the canonical shape", () => {
|
|
50
|
+
expect(new DiscordConnection().createJsonApi({ id: "u-1" })).toEqual({
|
|
51
|
+
data: { type: "discord-connections", id: "u-1", attributes: {}, relationships: {} },
|
|
52
|
+
included: [],
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("DiscordConnectIntent", () => {
|
|
58
|
+
it("rehydrates the intent returnPath and authorizeUrl", () => {
|
|
59
|
+
const model = new DiscordConnectIntent().rehydrate({
|
|
60
|
+
jsonApi: {
|
|
61
|
+
type: "discord-connect-intents",
|
|
62
|
+
id: "i-1",
|
|
63
|
+
attributes: {
|
|
64
|
+
returnPath: "/settings",
|
|
65
|
+
authorizeUrl: "http://api.test/discord-connect-intents/i-1/authorize",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
included: [],
|
|
69
|
+
} as any);
|
|
70
|
+
|
|
71
|
+
expect(model.returnPath).toBe("/settings");
|
|
72
|
+
expect(model.authorizeUrl).toBe("http://api.test/discord-connect-intents/i-1/authorize");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("DiscordConnectIntent createJsonApi sends the returnPath", () => {
|
|
76
|
+
expect(new DiscordConnectIntent().createJsonApi({ id: "i-1", returnPath: "/settings" })).toEqual({
|
|
77
|
+
data: { type: "discord-connect-intents", id: "i-1", attributes: { returnPath: "/settings" }, relationships: {} },
|
|
78
|
+
included: [],
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("DiscordConnectConfirmation", () => {
|
|
84
|
+
it("DiscordConnectConfirmation createJsonApi identifies the token", () => {
|
|
85
|
+
expect(new DiscordConnectConfirmation().createJsonApi({ id: "t-1" })).toEqual({
|
|
86
|
+
data: { type: "discord-connect-confirmations", id: "t-1", attributes: {}, relationships: {} },
|
|
87
|
+
included: [],
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("rehydrates result and discordName", () => {
|
|
92
|
+
const model = new DiscordConnectConfirmation().rehydrate({
|
|
93
|
+
jsonApi: {
|
|
94
|
+
type: "discord-connect-confirmations",
|
|
95
|
+
id: "t-1",
|
|
96
|
+
attributes: { result: "connected", discordName: "carlo" },
|
|
97
|
+
},
|
|
98
|
+
included: [],
|
|
99
|
+
} as any);
|
|
100
|
+
|
|
101
|
+
expect(model.result).toBe("connected");
|
|
102
|
+
expect(model.discordName).toBe("carlo");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("defaults a missing result to failed", () => {
|
|
106
|
+
const model = new DiscordConnectConfirmation().rehydrate({
|
|
107
|
+
jsonApi: { type: "discord-connect-confirmations", id: "t-1", attributes: {} },
|
|
108
|
+
included: [],
|
|
109
|
+
} as any);
|
|
110
|
+
|
|
111
|
+
expect(model.result).toBe("failed");
|
|
112
|
+
expect(model.discordName).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
|
|
3
|
+
export type DiscordConnectResult = "connected" | "taken" | "already" | "failed";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The outcome of confirming a Connect Discord return. `result` defaults to "failed".
|
|
7
|
+
*/
|
|
8
|
+
export interface DiscordConnectConfirmationInterface extends ApiDataInterface {
|
|
9
|
+
get result(): DiscordConnectResult;
|
|
10
|
+
get discordName(): string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type DiscordConnectConfirmationInput = {
|
|
14
|
+
/** The confirmation token from the `discordConnectConfirm` return parameter. */
|
|
15
|
+
id: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
|
|
2
|
+
import {
|
|
3
|
+
DiscordConnectConfirmationInput,
|
|
4
|
+
DiscordConnectConfirmationInterface,
|
|
5
|
+
} from "./discord-connect-confirmation.interface";
|
|
6
|
+
|
|
7
|
+
export class DiscordConnectConfirmationService extends AbstractService {
|
|
8
|
+
/**
|
|
9
|
+
* POST the confirmation token from the Discord return address. Links Discord
|
|
10
|
+
* only when the signed-in user created the intent; the result says what happened.
|
|
11
|
+
*/
|
|
12
|
+
static async create(params: DiscordConnectConfirmationInput): Promise<DiscordConnectConfirmationInterface> {
|
|
13
|
+
return this.callApi<DiscordConnectConfirmationInterface>({
|
|
14
|
+
type: Modules.DiscordConnectConfirmation,
|
|
15
|
+
method: HttpMethod.POST,
|
|
16
|
+
endpoint: new EndpointCreator({ endpoint: Modules.DiscordConnectConfirmation }).generate(),
|
|
17
|
+
input: params,
|
|
18
|
+
suppressGlobalError: true,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import {
|
|
3
|
+
DiscordConnectConfirmationInput,
|
|
4
|
+
DiscordConnectConfirmationInterface,
|
|
5
|
+
DiscordConnectResult,
|
|
6
|
+
} from "./discord-connect-confirmation.interface";
|
|
7
|
+
|
|
8
|
+
const RESULTS: readonly DiscordConnectResult[] = ["connected", "taken", "already", "failed"];
|
|
9
|
+
|
|
10
|
+
export class DiscordConnectConfirmation extends AbstractApiData implements DiscordConnectConfirmationInterface {
|
|
11
|
+
private _result: DiscordConnectResult = "failed";
|
|
12
|
+
private _discordName?: string;
|
|
13
|
+
|
|
14
|
+
get result(): DiscordConnectResult {
|
|
15
|
+
return this._result;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get discordName(): string | undefined {
|
|
19
|
+
return this._discordName;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
23
|
+
super.rehydrate(data);
|
|
24
|
+
|
|
25
|
+
const attributes = data.jsonApi.attributes ?? {};
|
|
26
|
+
|
|
27
|
+
this._result = RESULTS.includes(attributes.result) ? attributes.result : "failed";
|
|
28
|
+
this._discordName = attributes.discordName ?? undefined;
|
|
29
|
+
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createJsonApi(data: DiscordConnectConfirmationInput) {
|
|
34
|
+
const response: any = {
|
|
35
|
+
data: {
|
|
36
|
+
type: Modules.DiscordConnectConfirmation.name,
|
|
37
|
+
id: data.id,
|
|
38
|
+
attributes: {},
|
|
39
|
+
relationships: {},
|
|
40
|
+
},
|
|
41
|
+
included: [],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return response;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A one-time Connect Discord intent. Its id is a UUID the browser generates.
|
|
5
|
+
*/
|
|
6
|
+
export interface DiscordConnectIntentInterface extends ApiDataInterface {
|
|
7
|
+
get returnPath(): string;
|
|
8
|
+
get authorizeUrl(): string | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type DiscordConnectIntentInput = {
|
|
12
|
+
/** A UUID generated by the browser (uuid `v4()`). */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Where the user lands after Discord: `/`, `/settings` or `/settings?section=<id>`. */
|
|
15
|
+
returnPath: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
|
|
2
|
+
import { DiscordConnectIntentInput, DiscordConnectIntentInterface } from "./discord-connect-intent.interface";
|
|
3
|
+
|
|
4
|
+
export class DiscordConnectIntentService extends AbstractService {
|
|
5
|
+
/**
|
|
6
|
+
* POST a Connect Discord intent for the signed-in user. Sent with
|
|
7
|
+
* `credentials: "include"` so the browser stores the API's HttpOnly
|
|
8
|
+
* `discord_connect` cookie that binds the intent to this browser.
|
|
9
|
+
*/
|
|
10
|
+
static async create(params: DiscordConnectIntentInput): Promise<DiscordConnectIntentInterface> {
|
|
11
|
+
return this.callApi<DiscordConnectIntentInterface>({
|
|
12
|
+
type: Modules.DiscordConnectIntent,
|
|
13
|
+
method: HttpMethod.POST,
|
|
14
|
+
endpoint: new EndpointCreator({ endpoint: Modules.DiscordConnectIntent }).generate(),
|
|
15
|
+
input: params,
|
|
16
|
+
credentials: "include",
|
|
17
|
+
suppressGlobalError: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { AbstractApiData, JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { DiscordConnectIntentInput, DiscordConnectIntentInterface } from "./discord-connect-intent.interface";
|
|
3
|
+
|
|
4
|
+
export class DiscordConnectIntent extends AbstractApiData implements DiscordConnectIntentInterface {
|
|
5
|
+
private _returnPath?: string;
|
|
6
|
+
private _authorizeUrl?: string;
|
|
7
|
+
|
|
8
|
+
get returnPath(): string {
|
|
9
|
+
return this._returnPath ?? "/";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get authorizeUrl(): string | undefined {
|
|
13
|
+
return this._authorizeUrl;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
17
|
+
super.rehydrate(data);
|
|
18
|
+
|
|
19
|
+
const attributes = data.jsonApi.attributes ?? {};
|
|
20
|
+
|
|
21
|
+
this._returnPath = attributes.returnPath;
|
|
22
|
+
this._authorizeUrl = attributes.authorizeUrl ?? undefined;
|
|
23
|
+
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
createJsonApi(data: DiscordConnectIntentInput) {
|
|
28
|
+
const response: any = {
|
|
29
|
+
data: {
|
|
30
|
+
type: Modules.DiscordConnectIntent.name,
|
|
31
|
+
id: data.id,
|
|
32
|
+
attributes: {
|
|
33
|
+
returnPath: data.returnPath,
|
|
34
|
+
},
|
|
35
|
+
relationships: {},
|
|
36
|
+
},
|
|
37
|
+
included: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return response;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ApiDataInterface } from "../../../core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether the signed-in user has a Discord account attached. Not a stored entity:
|
|
5
|
+
* the API builds it for one user; its id is that user's id.
|
|
6
|
+
*/
|
|
7
|
+
export interface DiscordConnectionInterface extends ApiDataInterface {
|
|
8
|
+
get connected(): boolean;
|
|
9
|
+
get discordName(): string | undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules } from "../../../core";
|
|
2
|
+
import { DiscordConnectionInterface } from "./discord-connection.interface";
|
|
3
|
+
|
|
4
|
+
export class DiscordConnectionService extends AbstractService {
|
|
5
|
+
/**
|
|
6
|
+
* GET whether the user has Discord attached. `params.id` must be the signed-in
|
|
7
|
+
* user's id; any other id answers 404.
|
|
8
|
+
*/
|
|
9
|
+
static async findById(params: { id: string }): Promise<DiscordConnectionInterface> {
|
|
10
|
+
return this.callApi<DiscordConnectionInterface>({
|
|
11
|
+
type: Modules.DiscordConnection,
|
|
12
|
+
method: HttpMethod.GET,
|
|
13
|
+
endpoint: new EndpointCreator({ endpoint: Modules.DiscordConnection, id: params.id }).generate(),
|
|
14
|
+
suppressGlobalError: true,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|