+1
-1
api.js
+1
-1
api.js
···
84
84
85
85
class BlueskyAPI extends Minisky {
86
86
87
-
/** @param {string} host, @param {boolean} useAuthentication */
87
+
/** @param {string | undefined} host, @param {boolean} useAuthentication */
88
88
constructor(host, useAuthentication) {
89
89
super(host, useAuthentication ? new LocalStorageConfig() : undefined);
90
90
+53
-3
minisky.js
+53
-3
minisky.js
···
21
21
22
22
23
23
/**
24
+
* Thrown when DID or DID document is invalid.
25
+
*/
26
+
27
+
class DIDError extends Error {}
28
+
29
+
30
+
/**
24
31
* Base API client for connecting to an ATProto XRPC API.
25
32
*/
26
33
27
34
class Minisky {
28
35
36
+
/** @param {string} did, @returns {Promise<string>} */
37
+
38
+
static async pdsEndpointForDid(did) {
39
+
let url;
40
+
41
+
if (did.startsWith('did:plc:')) {
42
+
url = new URL(`https://plc.directory/${did}`);
43
+
} else if (did.startsWith('did:web:')) {
44
+
let host = did.replace(/^did:web:/, '');
45
+
url = new URL(`https://${host}/.well-known/did.json`);
46
+
} else {
47
+
throw new DIDError("Unknown DID type: " + did);
48
+
}
49
+
50
+
let response = await fetch(url);
51
+
let text = await response.text();
52
+
let json = text.trim().length > 0 ? JSON.parse(text) : undefined;
53
+
54
+
if (response.status == 200) {
55
+
let service = (json.service || []).find(s => s.id == '#atproto_pds');
56
+
if (service) {
57
+
return service.serviceEndpoint;
58
+
} else {
59
+
throw new DIDError("Missing #atproto_pds service definition");
60
+
}
61
+
} else {
62
+
throw new APIError(response.status, json);
63
+
}
64
+
}
65
+
29
66
/**
30
67
* @typedef {object} MiniskyOptions
31
68
* @prop {boolean} [sendAuthHeaders]
32
69
* @prop {boolean} [autoManageTokens]
33
70
*
34
-
* @param {string} host, @param {object} config, @param {MiniskyOptions} [options]
71
+
* @param {string | undefined} host, @param {object} config, @param {MiniskyOptions} [options]
35
72
*/
73
+
36
74
constructor(host, config, options) {
37
75
this.host = host;
38
76
this.config = config;
39
77
this.user = config?.user;
40
-
this.baseURL = (host.includes('://') ? host : `https://${host}`) + '/xrpc';
41
78
42
79
this.sendAuthHeaders = !!this.user;
43
80
this.autoManageTokens = !!this.user;
···
47
84
}
48
85
}
49
86
87
+
/** @returns {string} */
88
+
89
+
get baseURL() {
90
+
if (this.host) {
91
+
let host = (this.host.includes('://')) ? this.host : `https://${this.host}`;
92
+
return host + '/xrpc';
93
+
} else {
94
+
throw new AuthError('Hostname not set');
95
+
}
96
+
}
97
+
50
98
/** @returns {boolean} */
51
99
52
100
get isLoggedIn() {
53
-
return !!(this.user && this.user.accessToken && this.user.refreshToken && this.user.did);
101
+
return !!(this.user && this.user.accessToken && this.user.refreshToken && this.user.did && this.user.pdsEndpoint);
54
102
}
55
103
56
104
/**
···
210
258
this.user.accessToken = json['accessJwt'];
211
259
this.user.refreshToken = json['refreshJwt'];
212
260
this.user.did = json['did'];
261
+
this.user.pdsEndpoint = json['didDoc']['service'].find(s => s.id == '#atproto_pds')['serviceEndpoint'];
213
262
this.config.save();
214
263
}
215
264
···
217
266
delete this.user.accessToken;
218
267
delete this.user.refreshToken;
219
268
delete this.user.did;
269
+
delete this.user.pdsEndpoint;
220
270
this.config.save();
221
271
}
222
272
}
+25
-4
skythread.js
+25
-4
skythread.js
···
68
68
69
69
window.appView = new BlueskyAPI('api.bsky.app', false);
70
70
window.blueAPI = new BlueskyAPI('blue.mackuba.eu', false);
71
-
window.accountAPI = new BlueskyAPI('bsky.social', true);
71
+
window.accountAPI = new BlueskyAPI(undefined, true);
72
72
73
73
if (accountAPI.isLoggedIn && !isIncognito) {
74
74
window.api = accountAPI;
75
+
accountAPI.host = accountAPI.user.pdsEndpoint;
75
76
showLoggedInStatus(true, api.user.avatar);
76
77
} else if (accountAPI.isLoggedIn && isIncognito) {
77
78
window.api = appView;
79
+
accountAPI.host = accountAPI.user.pdsEndpoint;
78
80
showLoggedInStatus('incognito');
79
81
document.querySelector('#account_menu a[data-action=incognito]').innerText = '✓ Incognito mode';
80
82
} else {
···
213
215
214
216
if (submit.style.display == 'none') { return }
215
217
216
-
let pds = new BlueskyAPI('bsky.social', true);
217
-
218
218
handle.blur();
219
219
password.blur();
220
220
221
221
submit.style.display = 'none';
222
222
cloudy.style.display = 'inline-block';
223
223
224
-
pds.logIn(handle.value, password.value).then(() => {
224
+
logIn(handle.value, password.value).then((pds) => {
225
225
window.api = pds;
226
226
227
227
hideLogin();
···
237
237
238
238
window.setTimeout(() => alert(error), 10);
239
239
});
240
+
}
241
+
242
+
/** @param {string} identifier, @param {string} password, @returns {Promise<BlueskyAPI>} */
243
+
244
+
async function logIn(identifier, password) {
245
+
let pdsEndpoint;
246
+
247
+
if (identifier.match(/^did:/)) {
248
+
pdsEndpoint = await Minisky.pdsEndpointForDid(identifier);
249
+
} else if (identifier.match(/^[^@]+@[^@]+$/)) {
250
+
pdsEndpoint = 'bsky.social';
251
+
} else if (identifier.match(/^[\w\-]+(\.[\w\-]+)+$/)) {
252
+
let did = await appView.resolveHandle(identifier);
253
+
pdsEndpoint = await Minisky.pdsEndpointForDid(did);
254
+
} else {
255
+
throw 'Please enter your handle or DID';
256
+
}
257
+
258
+
let pds = new BlueskyAPI(pdsEndpoint, true);
259
+
await pds.logIn(identifier, password);
260
+
return pds;
240
261
}
241
262
242
263
function loadCurrentUserAvatar() {