+1
-1
api.js
+1
-1
api.js
+53
-3
minisky.js
+53
-3
minisky.js
···
21
22
23
/**
24
* Base API client for connecting to an ATProto XRPC API.
25
*/
26
27
class Minisky {
28
29
/**
30
* @typedef {object} MiniskyOptions
31
* @prop {boolean} [sendAuthHeaders]
32
* @prop {boolean} [autoManageTokens]
33
*
34
-
* @param {string} host, @param {object} config, @param {MiniskyOptions} [options]
35
*/
36
constructor(host, config, options) {
37
this.host = host;
38
this.config = config;
39
this.user = config?.user;
40
-
this.baseURL = (host.includes('://') ? host : `https://${host}`) + '/xrpc';
41
42
this.sendAuthHeaders = !!this.user;
43
this.autoManageTokens = !!this.user;
···
47
}
48
}
49
50
/** @returns {boolean} */
51
52
get isLoggedIn() {
53
-
return !!(this.user && this.user.accessToken && this.user.refreshToken && this.user.did);
54
}
55
56
/**
···
210
this.user.accessToken = json['accessJwt'];
211
this.user.refreshToken = json['refreshJwt'];
212
this.user.did = json['did'];
213
this.config.save();
214
}
215
···
217
delete this.user.accessToken;
218
delete this.user.refreshToken;
219
delete this.user.did;
220
this.config.save();
221
}
222
}
···
21
22
23
/**
24
+
* Thrown when DID or DID document is invalid.
25
+
*/
26
+
27
+
class DIDError extends Error {}
28
+
29
+
30
+
/**
31
* Base API client for connecting to an ATProto XRPC API.
32
*/
33
34
class Minisky {
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
+
66
/**
67
* @typedef {object} MiniskyOptions
68
* @prop {boolean} [sendAuthHeaders]
69
* @prop {boolean} [autoManageTokens]
70
*
71
+
* @param {string | undefined} host, @param {object} config, @param {MiniskyOptions} [options]
72
*/
73
+
74
constructor(host, config, options) {
75
this.host = host;
76
this.config = config;
77
this.user = config?.user;
78
79
this.sendAuthHeaders = !!this.user;
80
this.autoManageTokens = !!this.user;
···
84
}
85
}
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
+
98
/** @returns {boolean} */
99
100
get isLoggedIn() {
101
+
return !!(this.user && this.user.accessToken && this.user.refreshToken && this.user.did && this.user.pdsEndpoint);
102
}
103
104
/**
···
258
this.user.accessToken = json['accessJwt'];
259
this.user.refreshToken = json['refreshJwt'];
260
this.user.did = json['did'];
261
+
this.user.pdsEndpoint = json['didDoc']['service'].find(s => s.id == '#atproto_pds')['serviceEndpoint'];
262
this.config.save();
263
}
264
···
266
delete this.user.accessToken;
267
delete this.user.refreshToken;
268
delete this.user.did;
269
+
delete this.user.pdsEndpoint;
270
this.config.save();
271
}
272
}
+25
-4
skythread.js
+25
-4
skythread.js
···
68
69
window.appView = new BlueskyAPI('api.bsky.app', false);
70
window.blueAPI = new BlueskyAPI('blue.mackuba.eu', false);
71
-
window.accountAPI = new BlueskyAPI('bsky.social', true);
72
73
if (accountAPI.isLoggedIn && !isIncognito) {
74
window.api = accountAPI;
75
showLoggedInStatus(true, api.user.avatar);
76
} else if (accountAPI.isLoggedIn && isIncognito) {
77
window.api = appView;
78
showLoggedInStatus('incognito');
79
document.querySelector('#account_menu a[data-action=incognito]').innerText = '✓ Incognito mode';
80
} else {
···
213
214
if (submit.style.display == 'none') { return }
215
216
-
let pds = new BlueskyAPI('bsky.social', true);
217
-
218
handle.blur();
219
password.blur();
220
221
submit.style.display = 'none';
222
cloudy.style.display = 'inline-block';
223
224
-
pds.logIn(handle.value, password.value).then(() => {
225
window.api = pds;
226
227
hideLogin();
···
237
238
window.setTimeout(() => alert(error), 10);
239
});
240
}
241
242
function loadCurrentUserAvatar() {
···
68
69
window.appView = new BlueskyAPI('api.bsky.app', false);
70
window.blueAPI = new BlueskyAPI('blue.mackuba.eu', false);
71
+
window.accountAPI = new BlueskyAPI(undefined, true);
72
73
if (accountAPI.isLoggedIn && !isIncognito) {
74
window.api = accountAPI;
75
+
accountAPI.host = accountAPI.user.pdsEndpoint;
76
showLoggedInStatus(true, api.user.avatar);
77
} else if (accountAPI.isLoggedIn && isIncognito) {
78
window.api = appView;
79
+
accountAPI.host = accountAPI.user.pdsEndpoint;
80
showLoggedInStatus('incognito');
81
document.querySelector('#account_menu a[data-action=incognito]').innerText = '✓ Incognito mode';
82
} else {
···
215
216
if (submit.style.display == 'none') { return }
217
218
handle.blur();
219
password.blur();
220
221
submit.style.display = 'none';
222
cloudy.style.display = 'inline-block';
223
224
+
logIn(handle.value, password.value).then((pds) => {
225
window.api = pds;
226
227
hideLogin();
···
237
238
window.setTimeout(() => alert(error), 10);
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;
261
}
262
263
function loadCurrentUserAvatar() {