@logto/connector-discord 1.3.1 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +170 -195
- package/lib/index.js.map +1 -0
- package/package.json +11 -16
- package/lib/constant.d.ts +0 -19
- package/lib/index.d.ts +0 -14
- package/lib/mock.d.ts +0 -4
- package/lib/types.d.ts +0 -72
package/lib/index.js
CHANGED
|
@@ -1,215 +1,190 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { assert, conditional } from "@silverhand/essentials";
|
|
3
|
+
import { got, HTTPError } from "got";
|
|
4
|
+
import {
|
|
5
|
+
socialUserInfoGuard,
|
|
6
|
+
validateConfig,
|
|
7
|
+
ConnectorError,
|
|
8
|
+
ConnectorErrorCodes,
|
|
9
|
+
ConnectorType,
|
|
10
|
+
parseJson
|
|
11
|
+
} from "@logto/connector-kit";
|
|
4
12
|
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Discord exposes different versions of the API, You should specify which version to use by including it in your requests.
|
|
37
|
-
* https://discord.com/developers/docs/reference#api-reference
|
|
38
|
-
*/
|
|
39
|
-
const accessTokenEndpoint = 'https://discord.com/api/v10/oauth2/token';
|
|
40
|
-
const userInfoEndpoint = 'https://discord.com/api/v10/users/@me';
|
|
41
|
-
/**
|
|
42
|
-
* OAuth2 Scopes
|
|
43
|
-
* https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
|
|
44
|
-
*/
|
|
45
|
-
const scope = 'identify email';
|
|
46
|
-
const defaultMetadata = {
|
|
47
|
-
id: 'discord-universal',
|
|
48
|
-
target: 'discord',
|
|
49
|
-
platform: ConnectorPlatform.Universal,
|
|
50
|
-
name: {
|
|
51
|
-
en: 'Discord',
|
|
13
|
+
// src/constant.ts
|
|
14
|
+
import { ConnectorConfigFormItemType, ConnectorPlatform } from "@logto/connector-kit";
|
|
15
|
+
var authorizationEndpoint = "https://discord.com/oauth2/authorize";
|
|
16
|
+
var accessTokenEndpoint = "https://discord.com/api/v10/oauth2/token";
|
|
17
|
+
var userInfoEndpoint = "https://discord.com/api/v10/users/@me";
|
|
18
|
+
var scope = "identify email";
|
|
19
|
+
var defaultMetadata = {
|
|
20
|
+
id: "discord-universal",
|
|
21
|
+
target: "discord",
|
|
22
|
+
platform: ConnectorPlatform.Universal,
|
|
23
|
+
name: {
|
|
24
|
+
en: "Discord"
|
|
25
|
+
},
|
|
26
|
+
logo: "./logo.svg",
|
|
27
|
+
logoDark: null,
|
|
28
|
+
description: {
|
|
29
|
+
en: "Discord is the easiest way to talk over voice, video, and text.",
|
|
30
|
+
"pt-PT": "Discord \xE9 a forma mais f\xE1cil de comunicar por voz, v\xEDdeo e texto.",
|
|
31
|
+
"zh-CN": "Discord \u662F\u4E00\u6B3E\u4E13\u4E3A\u793E\u7FA4\u8BBE\u8BA1\u7684\u514D\u8D39\u7F51\u7EDC\u5B9E\u65F6\u901A\u8BDD\u8F6F\u4EF6\u4E0E\u6570\u5B57\u53D1\u884C\u5E73\u53F0\u3002",
|
|
32
|
+
"tr-TR": "Discord, sesli, g\xF6r\xFCnt\xFCl\xFC ve metin \xFCzerinden konu\u015Fman\u0131n en kolay yoludur.",
|
|
33
|
+
ko: "Discord\uB294 \uC74C\uC131, \uBE44\uB514\uC624 \uBC0F \uD14D\uC2A4\uD2B8\uB85C \uB300\uD654\uD558\uB294 \uAC00\uC7A5 \uC26C\uC6B4 \uBC29\uBC95\uC785\uB2C8\uB2E4."
|
|
34
|
+
},
|
|
35
|
+
readme: "./README.md",
|
|
36
|
+
formItems: [
|
|
37
|
+
{
|
|
38
|
+
key: "clientId",
|
|
39
|
+
type: ConnectorConfigFormItemType.Text,
|
|
40
|
+
required: true,
|
|
41
|
+
label: "Client ID",
|
|
42
|
+
placeholder: "<client-id>"
|
|
52
43
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'tr-TR': 'Discord, sesli, görüntülü ve metin üzerinden konuşmanın en kolay yoludur.',
|
|
60
|
-
ko: 'Discord는 음성, 비디오 및 텍스트로 대화하는 가장 쉬운 방법입니다.',
|
|
44
|
+
{
|
|
45
|
+
key: "clientSecret",
|
|
46
|
+
type: ConnectorConfigFormItemType.Text,
|
|
47
|
+
required: true,
|
|
48
|
+
label: "Client Secret",
|
|
49
|
+
placeholder: "<client-secret>"
|
|
61
50
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
key: 'clientSecret',
|
|
73
|
-
type: ConnectorConfigFormItemType.Text,
|
|
74
|
-
required: true,
|
|
75
|
-
label: 'Client Secret',
|
|
76
|
-
placeholder: '<client-secret>',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
key: 'scope',
|
|
80
|
-
type: ConnectorConfigFormItemType.Text,
|
|
81
|
-
required: false,
|
|
82
|
-
label: 'Scope',
|
|
83
|
-
placeholder: '<scope>',
|
|
84
|
-
description: "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.",
|
|
85
|
-
},
|
|
86
|
-
],
|
|
51
|
+
{
|
|
52
|
+
key: "scope",
|
|
53
|
+
type: ConnectorConfigFormItemType.Text,
|
|
54
|
+
required: false,
|
|
55
|
+
label: "Scope",
|
|
56
|
+
placeholder: "<scope>",
|
|
57
|
+
description: "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank."
|
|
58
|
+
}
|
|
59
|
+
]
|
|
87
60
|
};
|
|
88
|
-
|
|
61
|
+
var defaultTimeout = 5e3;
|
|
89
62
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return
|
|
63
|
+
// src/types.ts
|
|
64
|
+
import { z } from "zod";
|
|
65
|
+
var nullishToUndefined = (input) => {
|
|
66
|
+
if (!input) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
return input;
|
|
95
70
|
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
71
|
+
var discordConfigGuard = z.object({
|
|
72
|
+
clientId: z.string(),
|
|
73
|
+
clientSecret: z.string(),
|
|
74
|
+
scope: z.string().optional()
|
|
100
75
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
76
|
+
var accessTokenResponseGuard = z.object({
|
|
77
|
+
access_token: z.string(),
|
|
78
|
+
token_type: z.string(),
|
|
79
|
+
expires_in: z.number(),
|
|
80
|
+
scope: z.string()
|
|
106
81
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
82
|
+
var userInfoResponseGuard = z.object({
|
|
83
|
+
id: z.string(),
|
|
84
|
+
username: z.string().nullish().transform(nullishToUndefined),
|
|
85
|
+
avatar: z.string().nullish().transform(nullishToUndefined),
|
|
86
|
+
email: z.string().nullish().transform(nullishToUndefined),
|
|
87
|
+
verified: z.boolean().nullish().transform(nullishToUndefined)
|
|
113
88
|
});
|
|
114
|
-
z.object({
|
|
115
|
-
|
|
116
|
-
|
|
89
|
+
var authorizationCallbackErrorGuard = z.object({
|
|
90
|
+
error: z.string(),
|
|
91
|
+
error_description: z.string()
|
|
117
92
|
});
|
|
118
|
-
|
|
93
|
+
var authResponseGuard = z.object({ code: z.string(), redirectUri: z.string() });
|
|
119
94
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
state,
|
|
133
|
-
});
|
|
134
|
-
return `${authorizationEndpoint}?${queryParameters.toString()}`;
|
|
95
|
+
// src/index.ts
|
|
96
|
+
var getAuthorizationUri = (getConfig) => async ({ state, redirectUri }) => {
|
|
97
|
+
const config = await getConfig(defaultMetadata.id);
|
|
98
|
+
validateConfig(config, discordConfigGuard);
|
|
99
|
+
const queryParameters = new URLSearchParams({
|
|
100
|
+
client_id: config.clientId,
|
|
101
|
+
redirect_uri: redirectUri,
|
|
102
|
+
response_type: "code",
|
|
103
|
+
scope: config.scope ?? scope,
|
|
104
|
+
state
|
|
105
|
+
});
|
|
106
|
+
return `${authorizationEndpoint}?${queryParameters.toString()}`;
|
|
135
107
|
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
108
|
+
var getAccessToken = async (config, codeObject) => {
|
|
109
|
+
const { code, redirectUri } = codeObject;
|
|
110
|
+
const { clientId: client_id, clientSecret: client_secret } = config;
|
|
111
|
+
const httpResponse = await got.post(accessTokenEndpoint, {
|
|
112
|
+
form: {
|
|
113
|
+
client_id,
|
|
114
|
+
client_secret,
|
|
115
|
+
grant_type: "authorization_code",
|
|
116
|
+
code,
|
|
117
|
+
redirect_uri: redirectUri
|
|
118
|
+
},
|
|
119
|
+
timeout: { request: defaultTimeout }
|
|
120
|
+
});
|
|
121
|
+
const result = accessTokenResponseGuard.safeParse(parseJson(httpResponse.body));
|
|
122
|
+
if (!result.success) {
|
|
123
|
+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
|
|
124
|
+
}
|
|
125
|
+
const { access_token: accessToken } = result.data;
|
|
126
|
+
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
|
|
127
|
+
return { accessToken };
|
|
128
|
+
};
|
|
129
|
+
var getUserInfo = (getConfig) => async (data) => {
|
|
130
|
+
const { code, redirectUri } = await authorizationCallbackHandler(data);
|
|
131
|
+
const config = await getConfig(defaultMetadata.id);
|
|
132
|
+
validateConfig(config, discordConfigGuard);
|
|
133
|
+
const { accessToken } = await getAccessToken(config, { code, redirectUri });
|
|
134
|
+
try {
|
|
135
|
+
const httpResponse = await got.get(userInfoEndpoint, {
|
|
136
|
+
headers: {
|
|
137
|
+
authorization: `Bearer ${accessToken}`
|
|
138
|
+
},
|
|
139
|
+
timeout: { request: defaultTimeout }
|
|
148
140
|
});
|
|
149
|
-
const
|
|
141
|
+
const rawData = parseJson(httpResponse.body);
|
|
142
|
+
const result = userInfoResponseGuard.safeParse(rawData);
|
|
150
143
|
if (!result.success) {
|
|
151
|
-
|
|
144
|
+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
|
|
152
145
|
}
|
|
153
|
-
const {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const httpResponse = await got.get(userInfoEndpoint, {
|
|
164
|
-
headers: {
|
|
165
|
-
authorization: `Bearer ${accessToken}`,
|
|
166
|
-
},
|
|
167
|
-
timeout: { request: defaultTimeout },
|
|
168
|
-
});
|
|
169
|
-
const rawData = parseJson(httpResponse.body);
|
|
170
|
-
const result = userInfoResponseGuard.safeParse(rawData);
|
|
171
|
-
if (!result.success) {
|
|
172
|
-
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
|
|
173
|
-
}
|
|
174
|
-
const { id, username: name, avatar, email, verified } = result.data;
|
|
175
|
-
const rawUserInfo = {
|
|
176
|
-
id,
|
|
177
|
-
name,
|
|
178
|
-
avatar: conditional(avatar && `https://cdn.discordapp.com/avatars/${id}/${avatar}`),
|
|
179
|
-
email: conditional(verified && email),
|
|
180
|
-
};
|
|
181
|
-
const userInfoResult = socialUserInfoGuard.safeParse(rawUserInfo);
|
|
182
|
-
if (!userInfoResult.success) {
|
|
183
|
-
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
|
|
184
|
-
}
|
|
185
|
-
return { ...userInfoResult.data, rawData };
|
|
146
|
+
const { id, username: name, avatar, email, verified } = result.data;
|
|
147
|
+
const rawUserInfo = {
|
|
148
|
+
id,
|
|
149
|
+
name,
|
|
150
|
+
avatar: conditional(avatar && `https://cdn.discordapp.com/avatars/${id}/${avatar}`),
|
|
151
|
+
email: conditional(verified && email)
|
|
152
|
+
};
|
|
153
|
+
const userInfoResult = socialUserInfoGuard.safeParse(rawUserInfo);
|
|
154
|
+
if (!userInfoResult.success) {
|
|
155
|
+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
|
|
186
156
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
throw error;
|
|
157
|
+
return { ...userInfoResult.data, rawData };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof HTTPError) {
|
|
160
|
+
const { statusCode, body: rawBody } = error.response;
|
|
161
|
+
if (statusCode === 401) {
|
|
162
|
+
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
|
|
163
|
+
}
|
|
164
|
+
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
|
|
196
165
|
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
197
168
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
169
|
+
var authorizationCallbackHandler = async (parameterObject) => {
|
|
170
|
+
const result = authResponseGuard.safeParse(parameterObject);
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
|
|
173
|
+
}
|
|
174
|
+
return result.data;
|
|
204
175
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
176
|
+
var createDiscordConnector = async ({ getConfig }) => {
|
|
177
|
+
return {
|
|
178
|
+
metadata: defaultMetadata,
|
|
179
|
+
type: ConnectorType.Social,
|
|
180
|
+
configGuard: discordConfigGuard,
|
|
181
|
+
getAuthorizationUri: getAuthorizationUri(getConfig),
|
|
182
|
+
getUserInfo: getUserInfo(getConfig)
|
|
183
|
+
};
|
|
213
184
|
};
|
|
214
|
-
|
|
215
|
-
export {
|
|
185
|
+
var index_default = createDiscordConnector;
|
|
186
|
+
export {
|
|
187
|
+
index_default as default,
|
|
188
|
+
getAccessToken
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constant.ts","../src/types.ts"],"sourcesContent":["/**\n * Discord OAuth2 Connector\n * https://discord.com/developers/docs/topics/oauth2\n */\n\nimport { assert, conditional } from '@silverhand/essentials';\nimport { got, HTTPError } from 'got';\n\nimport type {\n GetConnectorConfig,\n GetAuthorizationUri,\n GetUserInfo,\n CreateConnector,\n SocialConnector,\n} from '@logto/connector-kit';\nimport {\n socialUserInfoGuard,\n validateConfig,\n ConnectorError,\n ConnectorErrorCodes,\n ConnectorType,\n parseJson,\n} from '@logto/connector-kit';\n\nimport {\n defaultMetadata,\n scope as defaultScope,\n authorizationEndpoint,\n accessTokenEndpoint,\n defaultTimeout,\n userInfoEndpoint,\n} from './constant.js';\nimport type { DiscordConfig } from './types.js';\nimport {\n discordConfigGuard,\n authResponseGuard,\n accessTokenResponseGuard,\n userInfoResponseGuard,\n} from './types.js';\n\nconst getAuthorizationUri =\n (getConfig: GetConnectorConfig): GetAuthorizationUri =>\n async ({ state, redirectUri }) => {\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, discordConfigGuard);\n\n const queryParameters = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n scope: config.scope ?? defaultScope,\n state,\n });\n\n return `${authorizationEndpoint}?${queryParameters.toString()}`;\n };\n\nexport const getAccessToken = async (\n config: DiscordConfig,\n codeObject: { code: string; redirectUri: string }\n) => {\n const { code, redirectUri } = codeObject;\n\n const { clientId: client_id, clientSecret: client_secret } = config;\n\n const httpResponse = await got.post(accessTokenEndpoint, {\n form: {\n client_id,\n client_secret,\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n },\n timeout: { request: defaultTimeout },\n });\n\n const result = accessTokenResponseGuard.safeParse(parseJson(httpResponse.body));\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);\n }\n\n const { access_token: accessToken } = result.data;\n\n assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));\n\n return { accessToken };\n};\n\nconst getUserInfo =\n (getConfig: GetConnectorConfig): GetUserInfo =>\n async (data) => {\n const { code, redirectUri } = await authorizationCallbackHandler(data);\n const config = await getConfig(defaultMetadata.id);\n validateConfig(config, discordConfigGuard);\n const { accessToken } = await getAccessToken(config, { code, redirectUri });\n\n try {\n const httpResponse = await got.get(userInfoEndpoint, {\n headers: {\n authorization: `Bearer ${accessToken}`,\n },\n timeout: { request: defaultTimeout },\n });\n const rawData = parseJson(httpResponse.body);\n const result = userInfoResponseGuard.safeParse(rawData);\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);\n }\n\n const { id, username: name, avatar, email, verified } = result.data;\n\n const rawUserInfo = {\n id,\n name,\n avatar: conditional(avatar && `https://cdn.discordapp.com/avatars/${id}/${avatar}`),\n email: conditional(verified && email),\n };\n\n const userInfoResult = socialUserInfoGuard.safeParse(rawUserInfo);\n\n if (!userInfoResult.success) {\n throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);\n }\n\n return { ...userInfoResult.data, rawData };\n } catch (error: unknown) {\n if (error instanceof HTTPError) {\n const { statusCode, body: rawBody } = error.response;\n\n if (statusCode === 401) {\n throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);\n }\n\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));\n }\n\n throw error;\n }\n };\n\nconst authorizationCallbackHandler = async (parameterObject: unknown) => {\n const result = authResponseGuard.safeParse(parameterObject);\n\n if (!result.success) {\n throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));\n }\n\n return result.data;\n};\n\nconst createDiscordConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {\n return {\n metadata: defaultMetadata,\n type: ConnectorType.Social,\n configGuard: discordConfigGuard,\n getAuthorizationUri: getAuthorizationUri(getConfig),\n getUserInfo: getUserInfo(getConfig),\n };\n};\n\nexport default createDiscordConnector;\n","import type { ConnectorMetadata } from '@logto/connector-kit';\nimport { ConnectorConfigFormItemType, ConnectorPlatform } from '@logto/connector-kit';\n\n/**\n * Base authorization URL.\n * https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-urls\n */\nexport const authorizationEndpoint = 'https://discord.com/oauth2/authorize';\n\n/**\n * Discord exposes different versions of the API, You should specify which version to use by including it in your requests.\n * https://discord.com/developers/docs/reference#api-reference\n */\nexport const accessTokenEndpoint = 'https://discord.com/api/v10/oauth2/token';\nexport const userInfoEndpoint = 'https://discord.com/api/v10/users/@me';\n\n/**\n * OAuth2 Scopes\n * https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes\n */\nexport const scope = 'identify email';\n\nexport const defaultMetadata: ConnectorMetadata = {\n id: 'discord-universal',\n target: 'discord',\n platform: ConnectorPlatform.Universal,\n name: {\n en: 'Discord',\n },\n logo: './logo.svg',\n logoDark: null,\n description: {\n en: 'Discord is the easiest way to talk over voice, video, and text.',\n 'pt-PT': 'Discord é a forma mais fácil de comunicar por voz, vídeo e texto.',\n 'zh-CN': 'Discord 是一款专为社群设计的免费网络实时通话软件与数字发行平台。',\n 'tr-TR': 'Discord, sesli, görüntülü ve metin üzerinden konuşmanın en kolay yoludur.',\n ko: 'Discord는 음성, 비디오 및 텍스트로 대화하는 가장 쉬운 방법입니다.',\n },\n readme: './README.md',\n formItems: [\n {\n key: 'clientId',\n type: ConnectorConfigFormItemType.Text,\n required: true,\n label: 'Client ID',\n placeholder: '<client-id>',\n },\n {\n key: 'clientSecret',\n type: ConnectorConfigFormItemType.Text,\n required: true,\n label: 'Client Secret',\n placeholder: '<client-secret>',\n },\n {\n key: 'scope',\n type: ConnectorConfigFormItemType.Text,\n required: false,\n label: 'Scope',\n placeholder: '<scope>',\n description:\n \"The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.\",\n },\n ],\n};\n\nexport const defaultTimeout = 5000;\n","import type { Nullable, Optional } from '@silverhand/essentials';\nimport { z } from 'zod';\n\nconst nullishToUndefined = <T = unknown>(input: Nullable<T>): Optional<T> => {\n if (!input) {\n return;\n }\n\n return input;\n};\n\nexport const discordConfigGuard = z.object({\n clientId: z.string(),\n clientSecret: z.string(),\n scope: z.string().optional(),\n});\n\nexport type DiscordConfig = z.infer<typeof discordConfigGuard>;\n\nexport const accessTokenResponseGuard = z.object({\n access_token: z.string(),\n token_type: z.string(),\n expires_in: z.number(),\n scope: z.string(),\n});\n\nexport type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;\n\nexport const userInfoResponseGuard = z.object({\n id: z.string(),\n username: z.string().nullish().transform(nullishToUndefined),\n avatar: z.string().nullish().transform(nullishToUndefined),\n email: z.string().nullish().transform(nullishToUndefined),\n verified: z.boolean().nullish().transform(nullishToUndefined),\n});\n\nexport type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;\n\nexport const authorizationCallbackErrorGuard = z.object({\n error: z.string(),\n error_description: z.string(),\n});\n\nexport const authResponseGuard = z.object({ code: z.string(), redirectUri: z.string() });\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/connector-discord",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Discord connector implementation.",
|
|
5
5
|
"author": "ZR3SYSTEMS. <https://github.com/FlurryNight>",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@logto/connector-kit": "^4.
|
|
7
|
+
"@logto/connector-kit": "^4.1.1",
|
|
8
8
|
"@silverhand/essentials": "^2.9.1",
|
|
9
9
|
"got": "^14.0.0",
|
|
10
|
-
"snakecase-keys": "^8.0.
|
|
11
|
-
"zod": "^3.
|
|
10
|
+
"snakecase-keys": "^8.0.1",
|
|
11
|
+
"zod": "^3.23.8"
|
|
12
12
|
},
|
|
13
13
|
"main": "./lib/index.js",
|
|
14
14
|
"module": "./lib/index.js",
|
|
@@ -41,30 +41,25 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@rollup/plugin-commonjs": "^26.0.0",
|
|
45
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
46
|
-
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
47
|
-
"@rollup/plugin-typescript": "^11.1.6",
|
|
48
44
|
"@silverhand/eslint-config": "6.0.1",
|
|
49
45
|
"@silverhand/ts-config": "6.0.0",
|
|
50
46
|
"@types/node": "^20.11.20",
|
|
51
47
|
"@types/supertest": "^6.0.2",
|
|
52
|
-
"@vitest/coverage-v8": "^1.
|
|
48
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
53
49
|
"eslint": "^8.56.0",
|
|
54
50
|
"lint-staged": "^15.0.2",
|
|
55
51
|
"nock": "^13.3.1",
|
|
56
52
|
"prettier": "^3.0.0",
|
|
57
|
-
"rollup": "^4.12.0",
|
|
58
|
-
"rollup-plugin-output-size": "^1.3.0",
|
|
59
53
|
"supertest": "^7.0.0",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
54
|
+
"tsup": "^8.3.0",
|
|
55
|
+
"typescript": "^5.5.3",
|
|
56
|
+
"vitest": "^2.1.9"
|
|
62
57
|
},
|
|
63
58
|
"scripts": {
|
|
64
59
|
"precommit": "lint-staged",
|
|
65
|
-
"
|
|
66
|
-
"build": "
|
|
67
|
-
"dev": "
|
|
60
|
+
"check": "tsc --noEmit",
|
|
61
|
+
"build": "tsup",
|
|
62
|
+
"dev": "tsup --watch",
|
|
68
63
|
"lint": "eslint --ext .ts src",
|
|
69
64
|
"lint:report": "pnpm lint --format json --output-file report.json",
|
|
70
65
|
"test": "vitest src",
|
package/lib/constant.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { ConnectorMetadata } from '@logto/connector-kit';
|
|
2
|
-
/**
|
|
3
|
-
* Base authorization URL.
|
|
4
|
-
* https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-urls
|
|
5
|
-
*/
|
|
6
|
-
export declare const authorizationEndpoint = "https://discord.com/oauth2/authorize";
|
|
7
|
-
/**
|
|
8
|
-
* Discord exposes different versions of the API, You should specify which version to use by including it in your requests.
|
|
9
|
-
* https://discord.com/developers/docs/reference#api-reference
|
|
10
|
-
*/
|
|
11
|
-
export declare const accessTokenEndpoint = "https://discord.com/api/v10/oauth2/token";
|
|
12
|
-
export declare const userInfoEndpoint = "https://discord.com/api/v10/users/@me";
|
|
13
|
-
/**
|
|
14
|
-
* OAuth2 Scopes
|
|
15
|
-
* https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
|
|
16
|
-
*/
|
|
17
|
-
export declare const scope = "identify email";
|
|
18
|
-
export declare const defaultMetadata: ConnectorMetadata;
|
|
19
|
-
export declare const defaultTimeout = 5000;
|
package/lib/index.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Discord OAuth2 Connector
|
|
3
|
-
* https://discord.com/developers/docs/topics/oauth2
|
|
4
|
-
*/
|
|
5
|
-
import type { CreateConnector, SocialConnector } from '@logto/connector-kit';
|
|
6
|
-
import type { DiscordConfig } from './types.js';
|
|
7
|
-
export declare const getAccessToken: (config: DiscordConfig, codeObject: {
|
|
8
|
-
code: string;
|
|
9
|
-
redirectUri: string;
|
|
10
|
-
}) => Promise<{
|
|
11
|
-
accessToken: string;
|
|
12
|
-
}>;
|
|
13
|
-
declare const createDiscordConnector: CreateConnector<SocialConnector>;
|
|
14
|
-
export default createDiscordConnector;
|
package/lib/mock.d.ts
DELETED
package/lib/types.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const discordConfigGuard: z.ZodObject<{
|
|
3
|
-
clientId: z.ZodString;
|
|
4
|
-
clientSecret: z.ZodString;
|
|
5
|
-
scope: z.ZodOptional<z.ZodString>;
|
|
6
|
-
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
clientId: string;
|
|
8
|
-
clientSecret: string;
|
|
9
|
-
scope?: string | undefined;
|
|
10
|
-
}, {
|
|
11
|
-
clientId: string;
|
|
12
|
-
clientSecret: string;
|
|
13
|
-
scope?: string | undefined;
|
|
14
|
-
}>;
|
|
15
|
-
export type DiscordConfig = z.infer<typeof discordConfigGuard>;
|
|
16
|
-
export declare const accessTokenResponseGuard: z.ZodObject<{
|
|
17
|
-
access_token: z.ZodString;
|
|
18
|
-
token_type: z.ZodString;
|
|
19
|
-
expires_in: z.ZodNumber;
|
|
20
|
-
scope: z.ZodString;
|
|
21
|
-
}, "strip", z.ZodTypeAny, {
|
|
22
|
-
scope: string;
|
|
23
|
-
access_token: string;
|
|
24
|
-
token_type: string;
|
|
25
|
-
expires_in: number;
|
|
26
|
-
}, {
|
|
27
|
-
scope: string;
|
|
28
|
-
access_token: string;
|
|
29
|
-
token_type: string;
|
|
30
|
-
expires_in: number;
|
|
31
|
-
}>;
|
|
32
|
-
export type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;
|
|
33
|
-
export declare const userInfoResponseGuard: z.ZodObject<{
|
|
34
|
-
id: z.ZodString;
|
|
35
|
-
username: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | undefined, string | null | undefined>;
|
|
36
|
-
avatar: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | undefined, string | null | undefined>;
|
|
37
|
-
email: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodString>>, string | undefined, string | null | undefined>;
|
|
38
|
-
verified: z.ZodEffects<z.ZodOptional<z.ZodNullable<z.ZodBoolean>>, boolean | undefined, boolean | null | undefined>;
|
|
39
|
-
}, "strip", z.ZodTypeAny, {
|
|
40
|
-
id: string;
|
|
41
|
-
username?: string | undefined;
|
|
42
|
-
avatar?: string | undefined;
|
|
43
|
-
email?: string | undefined;
|
|
44
|
-
verified?: boolean | undefined;
|
|
45
|
-
}, {
|
|
46
|
-
id: string;
|
|
47
|
-
username?: string | null | undefined;
|
|
48
|
-
avatar?: string | null | undefined;
|
|
49
|
-
email?: string | null | undefined;
|
|
50
|
-
verified?: boolean | null | undefined;
|
|
51
|
-
}>;
|
|
52
|
-
export type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;
|
|
53
|
-
export declare const authorizationCallbackErrorGuard: z.ZodObject<{
|
|
54
|
-
error: z.ZodString;
|
|
55
|
-
error_description: z.ZodString;
|
|
56
|
-
}, "strip", z.ZodTypeAny, {
|
|
57
|
-
error: string;
|
|
58
|
-
error_description: string;
|
|
59
|
-
}, {
|
|
60
|
-
error: string;
|
|
61
|
-
error_description: string;
|
|
62
|
-
}>;
|
|
63
|
-
export declare const authResponseGuard: z.ZodObject<{
|
|
64
|
-
code: z.ZodString;
|
|
65
|
-
redirectUri: z.ZodString;
|
|
66
|
-
}, "strip", z.ZodTypeAny, {
|
|
67
|
-
code: string;
|
|
68
|
-
redirectUri: string;
|
|
69
|
-
}, {
|
|
70
|
-
code: string;
|
|
71
|
-
redirectUri: string;
|
|
72
|
-
}>;
|