@aturi.to/waypoints 0.1.1 → 0.1.3
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/README.md +152 -8
- package/dist/index.cjs +702 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +292 -5
- package/dist/index.d.ts +292 -5
- package/dist/index.js +687 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,6 +54,11 @@ var COMPAT_FAMILIES = {
|
|
|
54
54
|
id: "blento",
|
|
55
55
|
name: "Blento",
|
|
56
56
|
description: "Blento profiles."
|
|
57
|
+
},
|
|
58
|
+
"atproto-explorer": {
|
|
59
|
+
id: "atproto-explorer",
|
|
60
|
+
name: "Record explorers",
|
|
61
|
+
description: "Raw AT Protocol record explorers (pdsls, atp.tools, Aturi Explore) that render any record by its AT URI."
|
|
57
62
|
}
|
|
58
63
|
};
|
|
59
64
|
var COMPAT_FAMILY_ORDER = [
|
|
@@ -67,8 +72,12 @@ var COMPAT_FAMILY_ORDER = [
|
|
|
67
72
|
"streamplace",
|
|
68
73
|
"popfeed",
|
|
69
74
|
"sifa",
|
|
70
|
-
"blento"
|
|
75
|
+
"blento",
|
|
76
|
+
"atproto-explorer"
|
|
71
77
|
];
|
|
78
|
+
function socialAppComposeIntent(origin) {
|
|
79
|
+
return { url: `${origin}/intent/compose`, textParam: "text" };
|
|
80
|
+
}
|
|
72
81
|
var WAYPOINT_DESTINATIONS_DATA = {
|
|
73
82
|
aturi: {
|
|
74
83
|
id: "aturi",
|
|
@@ -128,6 +137,11 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
128
137
|
},
|
|
129
138
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
130
139
|
category: "blueskyClients",
|
|
140
|
+
// Anisota implements the Bluesky shape deliberately, then extends it with
|
|
141
|
+
// `url`, `tags`, `channel`, and `title` (and answers `/post` with the same
|
|
142
|
+
// query). What's declared here is the interoperable subset every caller can
|
|
143
|
+
// rely on; the extras are Anisota-only and unrepresentable in this field.
|
|
144
|
+
composeIntent: socialAppComposeIntent("https://anisota.net"),
|
|
131
145
|
redirectCompat: ["bluesky-social", "standard-site"],
|
|
132
146
|
expectedCollections: ["app.bsky.", "net.anisota."]
|
|
133
147
|
},
|
|
@@ -144,12 +158,19 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
144
158
|
if (collection === "app.bsky.feed.post") {
|
|
145
159
|
return `https://bsky.app/profile/${handle}/post/${rkey}`;
|
|
146
160
|
}
|
|
161
|
+
if (collection === "app.bsky.graph.list") {
|
|
162
|
+
return `https://bsky.app/profile/${handle}/lists/${rkey}`;
|
|
163
|
+
}
|
|
147
164
|
return `https://bsky.app/profile/${handle}`;
|
|
148
165
|
}
|
|
149
166
|
return `https://bsky.app/profile/${handle}`;
|
|
150
167
|
},
|
|
151
168
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
152
169
|
category: "blueskyClients",
|
|
170
|
+
composeIntent: {
|
|
171
|
+
...socialAppComposeIntent("https://bsky.app"),
|
|
172
|
+
appUrl: "bluesky://intent/compose"
|
|
173
|
+
},
|
|
153
174
|
redirectCompat: ["bluesky-social"],
|
|
154
175
|
expectedCollections: ["app.bsky."]
|
|
155
176
|
},
|
|
@@ -166,12 +187,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
166
187
|
if (collection === "app.bsky.feed.post") {
|
|
167
188
|
return `https://blacksky.community/profile/${handle}/post/${rkey}`;
|
|
168
189
|
}
|
|
190
|
+
if (collection === "app.bsky.graph.list") {
|
|
191
|
+
return `https://blacksky.community/profile/${handle}/lists/${rkey}`;
|
|
192
|
+
}
|
|
169
193
|
return `https://blacksky.community/profile/${handle}`;
|
|
170
194
|
}
|
|
171
195
|
return `https://blacksky.community/profile/${handle}`;
|
|
172
196
|
},
|
|
173
197
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
174
198
|
category: "blueskyForks",
|
|
199
|
+
composeIntent: socialAppComposeIntent("https://blacksky.community"),
|
|
175
200
|
redirectCompat: ["bluesky-social"],
|
|
176
201
|
expectedCollections: ["app.bsky."]
|
|
177
202
|
},
|
|
@@ -180,7 +205,6 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
180
205
|
name: "Red Dwarf",
|
|
181
206
|
description: (collection) => {
|
|
182
207
|
if (collection === "app.bsky.feed.post") return "View post on reddwarf.app";
|
|
183
|
-
if (collection === "app.bsky.graph.list") return "View list on reddwarf.app";
|
|
184
208
|
return "View profile on reddwarf.app";
|
|
185
209
|
},
|
|
186
210
|
getUrl: (handle, collection, rkey) => {
|
|
@@ -192,8 +216,63 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
192
216
|
}
|
|
193
217
|
return `https://reddwarf.app/profile/${handle}`;
|
|
194
218
|
},
|
|
219
|
+
// No `list`: unlike the social-app forks in this family, Red Dwarf has no
|
|
220
|
+
// `/profile/:handle/lists/:rkey` route, so offering it for a list would
|
|
221
|
+
// silently land the user on the list's author instead.
|
|
222
|
+
supportedTypes: ["post", "profile", "record"],
|
|
223
|
+
category: "blueskyClients",
|
|
224
|
+
redirectCompat: ["bluesky-social"],
|
|
225
|
+
expectedCollections: ["app.bsky."]
|
|
226
|
+
},
|
|
227
|
+
impro: {
|
|
228
|
+
id: "impro",
|
|
229
|
+
name: "Impro",
|
|
230
|
+
description: (collection) => {
|
|
231
|
+
if (collection === "app.bsky.feed.post") return "View post on impro.social";
|
|
232
|
+
if (collection === "app.bsky.graph.list") return "View list on impro.social";
|
|
233
|
+
return "View profile on impro.social";
|
|
234
|
+
},
|
|
235
|
+
getUrl: (handle, collection, rkey) => {
|
|
236
|
+
if (collection && rkey) {
|
|
237
|
+
if (collection === "app.bsky.feed.post") {
|
|
238
|
+
return `https://impro.social/profile/${handle}/post/${rkey}`;
|
|
239
|
+
}
|
|
240
|
+
if (collection === "app.bsky.graph.list") {
|
|
241
|
+
return `https://impro.social/profile/${handle}/lists/${rkey}`;
|
|
242
|
+
}
|
|
243
|
+
return `https://impro.social/profile/${handle}`;
|
|
244
|
+
}
|
|
245
|
+
return `https://impro.social/profile/${handle}`;
|
|
246
|
+
},
|
|
195
247
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
196
|
-
category: "
|
|
248
|
+
category: "blueskyClients",
|
|
249
|
+
// Impro routes /intent/compose to its home view, which opens the composer
|
|
250
|
+
// for a signed-in user — but it never reads `?text`, so no textParam here.
|
|
251
|
+
composeIntent: { url: "https://impro.social/intent/compose" },
|
|
252
|
+
redirectCompat: ["bluesky-social"],
|
|
253
|
+
expectedCollections: ["app.bsky."]
|
|
254
|
+
},
|
|
255
|
+
lea: {
|
|
256
|
+
id: "lea",
|
|
257
|
+
name: "Lea",
|
|
258
|
+
description: (collection) => {
|
|
259
|
+
if (collection === "app.bsky.feed.post") return "View post on lea.ac";
|
|
260
|
+
return "View profile on lea.ac";
|
|
261
|
+
},
|
|
262
|
+
getUrl: (handle, collection, rkey) => {
|
|
263
|
+
if (collection && rkey) {
|
|
264
|
+
if (collection === "app.bsky.feed.post") {
|
|
265
|
+
return `https://lea.ac/profile/${handle}/post/${rkey}`;
|
|
266
|
+
}
|
|
267
|
+
return `https://lea.ac/profile/${handle}`;
|
|
268
|
+
}
|
|
269
|
+
return `https://lea.ac/profile/${handle}`;
|
|
270
|
+
},
|
|
271
|
+
// No `list`: like Red Dwarf, Lea serves only `/profile/:handle` and
|
|
272
|
+
// `/profile/:handle/post/:rkey` — `/lists/:rkey` 404s — so claiming list
|
|
273
|
+
// support would drop the user on an error page instead of the list.
|
|
274
|
+
supportedTypes: ["post", "profile", "record"],
|
|
275
|
+
category: "blueskyClients",
|
|
197
276
|
redirectCompat: ["bluesky-social"],
|
|
198
277
|
expectedCollections: ["app.bsky."]
|
|
199
278
|
},
|
|
@@ -222,7 +301,7 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
222
301
|
},
|
|
223
302
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
224
303
|
category: "devTools",
|
|
225
|
-
redirectCompat: []
|
|
304
|
+
redirectCompat: ["atproto-explorer"]
|
|
226
305
|
},
|
|
227
306
|
aturiExplore: {
|
|
228
307
|
id: "aturiExplore",
|
|
@@ -243,7 +322,7 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
243
322
|
},
|
|
244
323
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
245
324
|
category: "devTools",
|
|
246
|
-
redirectCompat: []
|
|
325
|
+
redirectCompat: ["atproto-explorer"]
|
|
247
326
|
},
|
|
248
327
|
atptools: {
|
|
249
328
|
id: "atptools",
|
|
@@ -258,7 +337,7 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
258
337
|
},
|
|
259
338
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
260
339
|
category: "devTools",
|
|
261
|
-
redirectCompat: []
|
|
340
|
+
redirectCompat: ["atproto-explorer"]
|
|
262
341
|
},
|
|
263
342
|
bluepy: {
|
|
264
343
|
id: "bluepy",
|
|
@@ -294,56 +373,94 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
294
373
|
if (collection === "app.bsky.feed.post") {
|
|
295
374
|
return `https://witchsky.app/profile/${handle}/post/${rkey}`;
|
|
296
375
|
}
|
|
376
|
+
if (collection === "app.bsky.graph.list") {
|
|
377
|
+
return `https://witchsky.app/profile/${handle}/lists/${rkey}`;
|
|
378
|
+
}
|
|
297
379
|
return `https://witchsky.app/profile/${handle}`;
|
|
298
380
|
}
|
|
299
381
|
return `https://witchsky.app/profile/${handle}`;
|
|
300
382
|
},
|
|
301
383
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
302
384
|
category: "blueskyForks",
|
|
385
|
+
composeIntent: socialAppComposeIntent("https://witchsky.app"),
|
|
303
386
|
redirectCompat: ["bluesky-social"],
|
|
304
387
|
expectedCollections: ["app.bsky."]
|
|
305
388
|
},
|
|
306
|
-
|
|
307
|
-
id: "
|
|
308
|
-
name: "
|
|
389
|
+
deer: {
|
|
390
|
+
id: "deer",
|
|
391
|
+
name: "Deer",
|
|
309
392
|
description: (collection) => {
|
|
310
|
-
if (collection === "app.bsky.feed.post") return "View post on
|
|
311
|
-
if (collection === "app.bsky.graph.list") return "View list on
|
|
312
|
-
return "View profile on
|
|
393
|
+
if (collection === "app.bsky.feed.post") return "View post on deer.social";
|
|
394
|
+
if (collection === "app.bsky.graph.list") return "View list on deer.social";
|
|
395
|
+
return "View profile on deer.social";
|
|
313
396
|
},
|
|
314
397
|
getUrl: (handle, collection, rkey) => {
|
|
315
398
|
if (collection && rkey) {
|
|
316
399
|
if (collection === "app.bsky.feed.post") {
|
|
317
|
-
return `https://
|
|
400
|
+
return `https://deer.social/profile/${handle}/post/${rkey}`;
|
|
318
401
|
}
|
|
319
|
-
|
|
402
|
+
if (collection === "app.bsky.graph.list") {
|
|
403
|
+
return `https://deer.social/profile/${handle}/lists/${rkey}`;
|
|
404
|
+
}
|
|
405
|
+
return `https://deer.social/profile/${handle}`;
|
|
320
406
|
}
|
|
321
|
-
return `https://
|
|
407
|
+
return `https://deer.social/profile/${handle}`;
|
|
322
408
|
},
|
|
323
409
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
324
410
|
category: "blueskyForks",
|
|
411
|
+
composeIntent: socialAppComposeIntent("https://deer.social"),
|
|
325
412
|
redirectCompat: ["bluesky-social"],
|
|
326
413
|
expectedCollections: ["app.bsky."]
|
|
327
414
|
},
|
|
328
|
-
|
|
329
|
-
id: "
|
|
330
|
-
name: "
|
|
415
|
+
mu: {
|
|
416
|
+
id: "mu",
|
|
417
|
+
name: "Mu",
|
|
331
418
|
description: (collection) => {
|
|
332
|
-
if (collection === "app.bsky.feed.post") return "View post on
|
|
333
|
-
if (collection === "app.bsky.graph.list") return "View list on
|
|
334
|
-
return "View profile on
|
|
419
|
+
if (collection === "app.bsky.feed.post") return "View post on mu.social";
|
|
420
|
+
if (collection === "app.bsky.graph.list") return "View list on mu.social";
|
|
421
|
+
return "View profile on mu.social";
|
|
335
422
|
},
|
|
336
423
|
getUrl: (handle, collection, rkey) => {
|
|
337
424
|
if (collection && rkey) {
|
|
338
425
|
if (collection === "app.bsky.feed.post") {
|
|
339
|
-
return `https://
|
|
426
|
+
return `https://mu.social/profile/${handle}/post/${rkey}`;
|
|
340
427
|
}
|
|
341
|
-
|
|
428
|
+
if (collection === "app.bsky.graph.list") {
|
|
429
|
+
return `https://mu.social/profile/${handle}/lists/${rkey}`;
|
|
430
|
+
}
|
|
431
|
+
return `https://mu.social/profile/${handle}`;
|
|
342
432
|
}
|
|
343
|
-
return `https://
|
|
433
|
+
return `https://mu.social/profile/${handle}`;
|
|
434
|
+
},
|
|
435
|
+
supportedTypes: ["post", "profile", "list", "record"],
|
|
436
|
+
category: "blueskyForks",
|
|
437
|
+
composeIntent: socialAppComposeIntent("https://mu.social"),
|
|
438
|
+
redirectCompat: ["bluesky-social"],
|
|
439
|
+
expectedCollections: ["app.bsky."]
|
|
440
|
+
},
|
|
441
|
+
northsky: {
|
|
442
|
+
id: "northsky",
|
|
443
|
+
name: "Northsky",
|
|
444
|
+
description: (collection) => {
|
|
445
|
+
if (collection === "app.bsky.feed.post") return "View post on northsky.app";
|
|
446
|
+
if (collection === "app.bsky.graph.list") return "View list on northsky.app";
|
|
447
|
+
return "View profile on northsky.app";
|
|
448
|
+
},
|
|
449
|
+
getUrl: (handle, collection, rkey) => {
|
|
450
|
+
if (collection && rkey) {
|
|
451
|
+
if (collection === "app.bsky.feed.post") {
|
|
452
|
+
return `https://northsky.app/profile/${handle}/post/${rkey}`;
|
|
453
|
+
}
|
|
454
|
+
if (collection === "app.bsky.graph.list") {
|
|
455
|
+
return `https://northsky.app/profile/${handle}/lists/${rkey}`;
|
|
456
|
+
}
|
|
457
|
+
return `https://northsky.app/profile/${handle}`;
|
|
458
|
+
}
|
|
459
|
+
return `https://northsky.app/profile/${handle}`;
|
|
344
460
|
},
|
|
345
461
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
346
462
|
category: "blueskyForks",
|
|
463
|
+
composeIntent: socialAppComposeIntent("https://northsky.app"),
|
|
347
464
|
redirectCompat: ["bluesky-social"],
|
|
348
465
|
expectedCollections: ["app.bsky."]
|
|
349
466
|
},
|
|
@@ -553,14 +670,54 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
553
670
|
category: "publications",
|
|
554
671
|
redirectCompat: ["standard-site"],
|
|
555
672
|
expectedCollections: ["pub.leaflet.", "site.standard."]
|
|
673
|
+
},
|
|
674
|
+
standardReader: {
|
|
675
|
+
id: "standardReader",
|
|
676
|
+
name: "Standard Reader",
|
|
677
|
+
description: (collection) => {
|
|
678
|
+
if (collection?.startsWith("site.standard.") || collection?.startsWith("pub.leaflet.")) {
|
|
679
|
+
return "Read document on standard-reader.app";
|
|
680
|
+
}
|
|
681
|
+
return "View documents on standard-reader.app";
|
|
682
|
+
},
|
|
683
|
+
getUrl: (handle, collection, rkey, did) => {
|
|
684
|
+
const identifier = did || handle;
|
|
685
|
+
if (collection && rkey && (collection.startsWith("site.standard.") || collection.startsWith("pub.leaflet."))) {
|
|
686
|
+
return `https://standard-reader.app/a/${identifier}/${rkey}`;
|
|
687
|
+
}
|
|
688
|
+
return `https://standard-reader.app/u/${identifier}`;
|
|
689
|
+
},
|
|
690
|
+
supportedTypes: ["post", "profile", "list", "record"],
|
|
691
|
+
category: "publications",
|
|
692
|
+
redirectCompat: ["standard-site"],
|
|
693
|
+
expectedCollections: ["pub.leaflet.", "site.standard."]
|
|
694
|
+
},
|
|
695
|
+
taproot: {
|
|
696
|
+
id: "taproot",
|
|
697
|
+
name: "Taproot",
|
|
698
|
+
description: (collection) => {
|
|
699
|
+
if (collection) return "Inspect record on atproto.at";
|
|
700
|
+
return "Browse repo on atproto.at";
|
|
701
|
+
},
|
|
702
|
+
getUrl: (handle, collection, rkey, did) => {
|
|
703
|
+
const identifier = did || handle;
|
|
704
|
+
if (collection && rkey) {
|
|
705
|
+
return `https://atproto.at/uri/at://${identifier}/${collection}/${rkey}`;
|
|
706
|
+
}
|
|
707
|
+
return `https://atproto.at/uri/at://${identifier}`;
|
|
708
|
+
},
|
|
709
|
+
supportedTypes: ["post", "profile", "list", "record"],
|
|
710
|
+
category: "devTools",
|
|
711
|
+
redirectCompat: []
|
|
556
712
|
}
|
|
557
713
|
};
|
|
558
714
|
var WAYPOINT_ORDER = [
|
|
559
715
|
"anisota",
|
|
560
716
|
"bluesky",
|
|
561
717
|
"bluepy",
|
|
562
|
-
"blacksky",
|
|
563
718
|
"reddwarf",
|
|
719
|
+
"impro",
|
|
720
|
+
"blacksky",
|
|
564
721
|
"leaflet",
|
|
565
722
|
"aturi",
|
|
566
723
|
"pinksky",
|
|
@@ -574,13 +731,17 @@ var WAYPOINT_ORDER = [
|
|
|
574
731
|
"anisotaReader",
|
|
575
732
|
"offprint",
|
|
576
733
|
"pckt",
|
|
734
|
+
"standardReader",
|
|
577
735
|
"aturiExplore",
|
|
578
736
|
"pdsls",
|
|
579
737
|
"tangled",
|
|
580
738
|
"atptools",
|
|
739
|
+
"taproot",
|
|
581
740
|
"witchsky",
|
|
582
|
-
"
|
|
583
|
-
"deer"
|
|
741
|
+
"mu",
|
|
742
|
+
"deer",
|
|
743
|
+
"lea",
|
|
744
|
+
"northsky"
|
|
584
745
|
];
|
|
585
746
|
function getWaypointDataForType(type) {
|
|
586
747
|
return WAYPOINT_ORDER.map((id) => WAYPOINT_DESTINATIONS_DATA[id]).filter((waypoint) => waypoint.supportedTypes.includes(type));
|
|
@@ -674,13 +835,13 @@ var RECOMMENDED_WAYPOINTS = {
|
|
|
674
835
|
label: "Recommended for Repos"
|
|
675
836
|
},
|
|
676
837
|
"record": {
|
|
677
|
-
waypointIds: ["aturiExplore", "pdsls", "atptools"],
|
|
838
|
+
waypointIds: ["aturiExplore", "pdsls", "atptools", "taproot"],
|
|
678
839
|
label: "Recommended for Records"
|
|
679
840
|
}
|
|
680
841
|
};
|
|
681
842
|
var RECOMMENDED_NAMESPACE_PREFIXES = {
|
|
682
843
|
"site.standard": {
|
|
683
|
-
waypointIds: ["leaflet", "anisotaReader", "offprint", "pckt", "pdsls"],
|
|
844
|
+
waypointIds: ["leaflet", "standardReader", "anisotaReader", "offprint", "pckt", "pdsls"],
|
|
684
845
|
label: "Recommended for Publications"
|
|
685
846
|
},
|
|
686
847
|
"pub.leaflet": {
|
|
@@ -747,6 +908,67 @@ function waypointActivity(waypoint, repoCollections) {
|
|
|
747
908
|
}
|
|
748
909
|
return "absent";
|
|
749
910
|
}
|
|
911
|
+
var COMPOSE_INTENT_TEXT_PLACEHOLDER = "{text}";
|
|
912
|
+
function supportsComposeIntent(waypoint) {
|
|
913
|
+
return !!waypoint.composeIntent;
|
|
914
|
+
}
|
|
915
|
+
function getComposeIntentUrl(waypoint, text) {
|
|
916
|
+
const intent = waypoint.composeIntent;
|
|
917
|
+
if (!intent) return null;
|
|
918
|
+
return appendComposeText(intent.url, intent.textParam, text);
|
|
919
|
+
}
|
|
920
|
+
function getComposeIntentAppUrl(waypoint, text) {
|
|
921
|
+
const intent = waypoint.composeIntent;
|
|
922
|
+
if (!intent?.appUrl) return null;
|
|
923
|
+
return appendComposeText(intent.appUrl, intent.textParam, text);
|
|
924
|
+
}
|
|
925
|
+
function getComposeIntentTemplate(waypoint) {
|
|
926
|
+
const intent = waypoint.composeIntent;
|
|
927
|
+
if (!intent) return null;
|
|
928
|
+
if (!intent.textParam) return intent.url;
|
|
929
|
+
return `${intent.url}?${intent.textParam}=${COMPOSE_INTENT_TEXT_PLACEHOLDER}`;
|
|
930
|
+
}
|
|
931
|
+
function getComposeIntentWaypoints(type) {
|
|
932
|
+
const waypoints = type ? getWaypointDataForType(type) : WAYPOINT_ORDER.map((id) => WAYPOINT_DESTINATIONS_DATA[id]).filter(Boolean);
|
|
933
|
+
return waypoints.filter(supportsComposeIntent);
|
|
934
|
+
}
|
|
935
|
+
function describeComposeIntent(waypoint, text) {
|
|
936
|
+
const intent = waypoint.composeIntent;
|
|
937
|
+
if (!intent) return null;
|
|
938
|
+
const descriptor = {
|
|
939
|
+
url: getComposeIntentUrl(waypoint, text),
|
|
940
|
+
urlTemplate: getComposeIntentTemplate(waypoint),
|
|
941
|
+
textParam: intent.textParam ?? null,
|
|
942
|
+
prefillsText: !!intent.textParam
|
|
943
|
+
};
|
|
944
|
+
const appUrl = getComposeIntentAppUrl(waypoint, text);
|
|
945
|
+
if (appUrl) descriptor.appUrl = appUrl;
|
|
946
|
+
return descriptor;
|
|
947
|
+
}
|
|
948
|
+
function appendComposeText(base, textParam, text) {
|
|
949
|
+
if (!textParam || !text) return base;
|
|
950
|
+
return `${base}?${textParam}=${encodeURIComponent(text)}`;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/upstreamFetch.ts
|
|
954
|
+
var RETRY_DELAY_MS = 250;
|
|
955
|
+
async function upstreamFetch(url, { timeoutMs = 8e3, retries = 1, ...init } = {}) {
|
|
956
|
+
let lastError;
|
|
957
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
958
|
+
try {
|
|
959
|
+
return await fetch(url, {
|
|
960
|
+
...init,
|
|
961
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
962
|
+
});
|
|
963
|
+
} catch (error) {
|
|
964
|
+
lastError = error;
|
|
965
|
+
if (attempt < retries) {
|
|
966
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
throw lastError;
|
|
971
|
+
}
|
|
750
972
|
|
|
751
973
|
// src/uriParser.ts
|
|
752
974
|
function parseURI(handle, collection, rkey) {
|
|
@@ -789,25 +1011,29 @@ function parseURI(handle, collection, rkey) {
|
|
|
789
1011
|
error: "Invalid URI structure"
|
|
790
1012
|
};
|
|
791
1013
|
}
|
|
792
|
-
async function
|
|
1014
|
+
async function resolveHandleStatus(handle) {
|
|
793
1015
|
if (handle.startsWith("did:")) {
|
|
794
|
-
return handle;
|
|
1016
|
+
return { did: handle };
|
|
795
1017
|
}
|
|
796
1018
|
try {
|
|
797
1019
|
const apiUrl = process.env.NEXT_PUBLIC_BSKY_API_URL || "https://public.api.bsky.app";
|
|
798
|
-
const response = await
|
|
1020
|
+
const response = await upstreamFetch(
|
|
799
1021
|
`${apiUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`
|
|
800
1022
|
);
|
|
801
1023
|
if (!response.ok) {
|
|
802
|
-
return null;
|
|
1024
|
+
return { did: null, reason: response.status >= 500 ? "unavailable" : "not-found" };
|
|
803
1025
|
}
|
|
804
1026
|
const data = await response.json();
|
|
805
|
-
return data.did
|
|
1027
|
+
if (data.did) return { did: data.did };
|
|
1028
|
+
return { did: null, reason: "not-found" };
|
|
806
1029
|
} catch (error) {
|
|
807
1030
|
console.error("Error resolving handle:", error);
|
|
808
|
-
return null;
|
|
1031
|
+
return { did: null, reason: "unavailable" };
|
|
809
1032
|
}
|
|
810
1033
|
}
|
|
1034
|
+
async function resolveHandle(handle) {
|
|
1035
|
+
return (await resolveHandleStatus(handle)).did;
|
|
1036
|
+
}
|
|
811
1037
|
function getDisplayName(handle, did) {
|
|
812
1038
|
if (handle.startsWith("did:")) {
|
|
813
1039
|
return did ? `@${did.slice(0, 16)}...` : "Unknown";
|
|
@@ -820,16 +1046,30 @@ var BLUESKY_FAMILY = [
|
|
|
820
1046
|
{ source: "bluesky", hosts: ["bsky.app"] },
|
|
821
1047
|
{ source: "blacksky", hosts: ["blacksky.community"] },
|
|
822
1048
|
{ source: "reddwarf", hosts: ["reddwarf.app"] },
|
|
1049
|
+
{ source: "impro", hosts: ["impro.social"] },
|
|
1050
|
+
{ source: "lea", hosts: ["lea.ac"] },
|
|
823
1051
|
{ source: "witchsky", hosts: ["witchsky.app"] },
|
|
824
|
-
{ source: "catsky", hosts: ["catsky.social"] },
|
|
825
1052
|
{ source: "deer", hosts: ["deer.social"] },
|
|
826
|
-
{ source: "
|
|
1053
|
+
{ source: "northsky", hosts: ["northsky.app"] },
|
|
1054
|
+
{ source: "mu", hosts: ["mu.social"] },
|
|
1055
|
+
{ source: "anisota", hosts: ["anisota.net"], matchSubdomains: true }
|
|
827
1056
|
];
|
|
1057
|
+
var SUBDOMAIN_HOSTS = BLUESKY_FAMILY.filter((f) => f.matchSubdomains).flatMap(
|
|
1058
|
+
(f) => f.hosts
|
|
1059
|
+
);
|
|
828
1060
|
function normalizeHost(host) {
|
|
829
1061
|
return host.toLowerCase().replace(/^www\./, "");
|
|
830
1062
|
}
|
|
1063
|
+
function isSubdomainOf(host, base) {
|
|
1064
|
+
return host.endsWith(`.${base}`);
|
|
1065
|
+
}
|
|
1066
|
+
function hostMatchesConfig(host, config) {
|
|
1067
|
+
if (config.hosts.includes(host)) return true;
|
|
1068
|
+
if (!config.matchSubdomains) return false;
|
|
1069
|
+
return config.hosts.some((h) => isSubdomainOf(host, h));
|
|
1070
|
+
}
|
|
831
1071
|
function matchBlueskyFamily(host, parts) {
|
|
832
|
-
const entry = BLUESKY_FAMILY.find((f) =>
|
|
1072
|
+
const entry = BLUESKY_FAMILY.find((f) => hostMatchesConfig(host, f));
|
|
833
1073
|
if (!entry) return null;
|
|
834
1074
|
if (parts[0] !== "profile" || !parts[1]) return null;
|
|
835
1075
|
const handle = parts[1];
|
|
@@ -871,6 +1111,19 @@ function matchBlueskyFamily(host, parts) {
|
|
|
871
1111
|
}
|
|
872
1112
|
};
|
|
873
1113
|
}
|
|
1114
|
+
if (parts[2] === "document" && parts[3]) {
|
|
1115
|
+
return {
|
|
1116
|
+
source: entry.source,
|
|
1117
|
+
parsed: {
|
|
1118
|
+
type: "record",
|
|
1119
|
+
uri: `at://${handle}/site.standard.document/${parts[3]}`,
|
|
1120
|
+
handle,
|
|
1121
|
+
did,
|
|
1122
|
+
collection: "site.standard.document",
|
|
1123
|
+
rkey: parts[3]
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
874
1127
|
return {
|
|
875
1128
|
source: entry.source,
|
|
876
1129
|
parsed: {
|
|
@@ -1141,11 +1394,160 @@ function matchBlento(host, parts) {
|
|
|
1141
1394
|
parsed: { type: "profile", uri: `at://${handle}`, handle, did }
|
|
1142
1395
|
};
|
|
1143
1396
|
}
|
|
1397
|
+
function matchStandardReader(host, parts) {
|
|
1398
|
+
if (host !== "standard-reader.app") return null;
|
|
1399
|
+
if (parts[0] === "a" && parts[1] && parts[2]) {
|
|
1400
|
+
const handle = parts[1];
|
|
1401
|
+
const did = handle.startsWith("did:") ? handle : void 0;
|
|
1402
|
+
const rkey = parts[2];
|
|
1403
|
+
return {
|
|
1404
|
+
source: "standardReader",
|
|
1405
|
+
parsed: {
|
|
1406
|
+
type: "record",
|
|
1407
|
+
uri: `at://${handle}/site.standard.document/${rkey}`,
|
|
1408
|
+
handle,
|
|
1409
|
+
did,
|
|
1410
|
+
collection: "site.standard.document",
|
|
1411
|
+
rkey
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
if (parts[0] === "u" && parts[1]) {
|
|
1416
|
+
const handle = parts[1];
|
|
1417
|
+
const did = handle.startsWith("did:") ? handle : void 0;
|
|
1418
|
+
return {
|
|
1419
|
+
source: "standardReader",
|
|
1420
|
+
parsed: { type: "profile", uri: `at://${handle}`, handle, did }
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
return null;
|
|
1424
|
+
}
|
|
1425
|
+
function matchFlatRecordHost(host, parts, target) {
|
|
1426
|
+
if (host !== target.host) return null;
|
|
1427
|
+
const [handle, collection, rkey] = parts;
|
|
1428
|
+
if (!handle || !collection || !rkey) return null;
|
|
1429
|
+
if (!collection.includes(".")) return null;
|
|
1430
|
+
const did = handle.startsWith("did:") ? handle : void 0;
|
|
1431
|
+
return {
|
|
1432
|
+
source: target.source,
|
|
1433
|
+
parsed: {
|
|
1434
|
+
type: inferType(collection),
|
|
1435
|
+
uri: `at://${handle}/${collection}/${rkey}`,
|
|
1436
|
+
handle,
|
|
1437
|
+
did,
|
|
1438
|
+
collection,
|
|
1439
|
+
rkey
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
function matchTaproot(host, pathname) {
|
|
1444
|
+
if (host !== "atproto.at") return null;
|
|
1445
|
+
const m = pathname.replace(/^\/+/, "").match(/^uri\/at:\/{1,2}(.+)$/);
|
|
1446
|
+
if (!m) return null;
|
|
1447
|
+
const segments = m[1].split("/").filter(Boolean);
|
|
1448
|
+
if (segments.length === 0) return null;
|
|
1449
|
+
const handle = segments[0];
|
|
1450
|
+
const collection = segments[1];
|
|
1451
|
+
const rkey = segments[2];
|
|
1452
|
+
const did = handle.startsWith("did:") ? handle : void 0;
|
|
1453
|
+
if (collection && rkey) {
|
|
1454
|
+
return {
|
|
1455
|
+
source: "taproot",
|
|
1456
|
+
parsed: {
|
|
1457
|
+
type: inferType(collection),
|
|
1458
|
+
uri: `at://${handle}/${collection}/${rkey}`,
|
|
1459
|
+
handle,
|
|
1460
|
+
did,
|
|
1461
|
+
collection,
|
|
1462
|
+
rkey
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
source: "taproot",
|
|
1468
|
+
parsed: { type: "profile", uri: `at://${handle}`, handle, did }
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
function matchAturi(host, parts) {
|
|
1472
|
+
if (host !== "aturi.to") return null;
|
|
1473
|
+
const isIdentifier = (seg2) => !!seg2 && (seg2.startsWith("did:") || seg2.includes("."));
|
|
1474
|
+
if (parts[0] === "explore" && isIdentifier(parts[1])) {
|
|
1475
|
+
const handle = parts[1];
|
|
1476
|
+
const did = handle.startsWith("did:") ? handle : void 0;
|
|
1477
|
+
const collection = parts[2];
|
|
1478
|
+
const rkey = parts[3];
|
|
1479
|
+
if (collection && rkey) {
|
|
1480
|
+
return {
|
|
1481
|
+
source: "aturiExplore",
|
|
1482
|
+
parsed: {
|
|
1483
|
+
type: inferType(collection),
|
|
1484
|
+
uri: `at://${handle}/${collection}/${rkey}`,
|
|
1485
|
+
handle,
|
|
1486
|
+
did,
|
|
1487
|
+
collection,
|
|
1488
|
+
rkey
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
return {
|
|
1493
|
+
source: "aturiExplore",
|
|
1494
|
+
parsed: { type: "profile", uri: `at://${handle}`, handle, did }
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
if (parts[0] === "profile" && isIdentifier(parts[1])) {
|
|
1498
|
+
const handle = parts[1];
|
|
1499
|
+
const did = handle.startsWith("did:") ? handle : void 0;
|
|
1500
|
+
if (parts[2] === "post" && parts[3]) {
|
|
1501
|
+
return {
|
|
1502
|
+
source: "aturi",
|
|
1503
|
+
parsed: {
|
|
1504
|
+
type: "post",
|
|
1505
|
+
uri: `at://${handle}/app.bsky.feed.post/${parts[3]}`,
|
|
1506
|
+
handle,
|
|
1507
|
+
did,
|
|
1508
|
+
collection: "app.bsky.feed.post",
|
|
1509
|
+
rkey: parts[3]
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
if (parts[2] === "lists" && parts[3]) {
|
|
1514
|
+
return {
|
|
1515
|
+
source: "aturi",
|
|
1516
|
+
parsed: {
|
|
1517
|
+
type: "list",
|
|
1518
|
+
uri: `at://${handle}/app.bsky.graph.list/${parts[3]}`,
|
|
1519
|
+
handle,
|
|
1520
|
+
did,
|
|
1521
|
+
collection: "app.bsky.graph.list",
|
|
1522
|
+
rkey: parts[3]
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
if (parts[2] && parts[2].includes(".") && parts[3]) {
|
|
1527
|
+
return {
|
|
1528
|
+
source: "aturi",
|
|
1529
|
+
parsed: {
|
|
1530
|
+
type: inferType(parts[2]),
|
|
1531
|
+
uri: `at://${handle}/${parts[2]}/${parts[3]}`,
|
|
1532
|
+
handle,
|
|
1533
|
+
did,
|
|
1534
|
+
collection: parts[2],
|
|
1535
|
+
rkey: parts[3]
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
return {
|
|
1540
|
+
source: "aturi",
|
|
1541
|
+
parsed: { type: "profile", uri: `at://${handle}`, handle, did }
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1144
1546
|
function matchSupportedUrl(url) {
|
|
1145
1547
|
const host = normalizeHost(url.hostname);
|
|
1146
1548
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
1147
1549
|
const search = url.searchParams;
|
|
1148
|
-
return matchBlueskyFamily(host, parts) || matchPinksky(host, parts, search) || matchLeaflet(host, parts) || matchTangled(host, parts) || matchMargin(host, parts) || matchPdsls(host, url.pathname) || matchAtpTools(host, url.pathname) || matchBluepy(host, url.pathname) || matchSemble(host, parts) || matchStreamplace(host, parts) || matchGrain(host, parts) || matchPopfeed(host, parts) || matchSifa(host, parts) || matchBlento(host, parts);
|
|
1550
|
+
return matchAturi(host, parts) || matchBlueskyFamily(host, parts) || matchPinksky(host, parts, search) || matchLeaflet(host, parts) || matchTangled(host, parts) || matchMargin(host, parts) || matchPdsls(host, url.pathname) || matchAtpTools(host, url.pathname) || matchBluepy(host, url.pathname) || matchSemble(host, parts) || matchStreamplace(host, parts) || matchGrain(host, parts) || matchPopfeed(host, parts) || matchSifa(host, parts) || matchBlento(host, parts) || matchStandardReader(host, parts) || matchFlatRecordHost(host, parts, { host: "offprint.app", source: "offprint" }) || matchFlatRecordHost(host, parts, { host: "pckt.blog", source: "pckt" }) || matchTaproot(host, url.pathname);
|
|
1149
1551
|
}
|
|
1150
1552
|
function parseAtUri(uri) {
|
|
1151
1553
|
if (!uri.startsWith("at://")) return null;
|
|
@@ -1175,6 +1577,7 @@ function parseAtUri(uri) {
|
|
|
1175
1577
|
};
|
|
1176
1578
|
}
|
|
1177
1579
|
var SUPPORTED_HOSTS = [
|
|
1580
|
+
"aturi.to",
|
|
1178
1581
|
...BLUESKY_FAMILY.flatMap((e) => e.hosts),
|
|
1179
1582
|
"pinkleap.app",
|
|
1180
1583
|
"leaflet.pub",
|
|
@@ -1188,8 +1591,17 @@ var SUPPORTED_HOSTS = [
|
|
|
1188
1591
|
"grain.social",
|
|
1189
1592
|
"popfeed.social",
|
|
1190
1593
|
"sifa.id",
|
|
1191
|
-
"blento.app"
|
|
1594
|
+
"blento.app",
|
|
1595
|
+
"standard-reader.app",
|
|
1596
|
+
"offprint.app",
|
|
1597
|
+
"pckt.blog",
|
|
1598
|
+
"atproto.at"
|
|
1192
1599
|
];
|
|
1600
|
+
function isSupportedHost(host) {
|
|
1601
|
+
const h = normalizeHost(host);
|
|
1602
|
+
if (SUPPORTED_HOSTS.includes(h)) return true;
|
|
1603
|
+
return SUBDOMAIN_HOSTS.some((base) => isSubdomainOf(h, base));
|
|
1604
|
+
}
|
|
1193
1605
|
|
|
1194
1606
|
// src/resolve.ts
|
|
1195
1607
|
var DID_REQUIRED_WAYPOINTS = /* @__PURE__ */ new Set([
|
|
@@ -1207,18 +1619,26 @@ function buildWaypointsForParsed(parsed, options = {}) {
|
|
|
1207
1619
|
if (DID_REQUIRED_WAYPOINTS.has(w.id) && !did) return null;
|
|
1208
1620
|
const url = w.getUrl(parsed.handle, parsed.collection, parsed.rkey, did);
|
|
1209
1621
|
if (!url) return null;
|
|
1210
|
-
return {
|
|
1622
|
+
return {
|
|
1623
|
+
id: w.id,
|
|
1624
|
+
name: w.name,
|
|
1625
|
+
category: w.category,
|
|
1626
|
+
url,
|
|
1627
|
+
composeIntent: describeComposeIntent(w, options.composeText)
|
|
1628
|
+
};
|
|
1211
1629
|
}).filter((w) => !!w);
|
|
1212
1630
|
const recommendedRaw = getRecommendedWaypointsData(type, parsed.collection);
|
|
1213
1631
|
const availableIds = new Set(waypoints.map((w) => w.id));
|
|
1214
1632
|
const ids = recommendedRaw.waypoints.map((w) => w.id).filter((id) => id !== exclude && availableIds.has(id));
|
|
1215
1633
|
return { waypoints, recommended: { ids, label: recommendedRaw.label } };
|
|
1216
1634
|
}
|
|
1217
|
-
function resolveAtUri(uri) {
|
|
1635
|
+
function resolveAtUri(uri, options = {}) {
|
|
1218
1636
|
const match = parseAtUri(uri);
|
|
1219
1637
|
if (!match) return null;
|
|
1220
1638
|
const { parsed, source } = match;
|
|
1221
|
-
const { waypoints, recommended } = buildWaypointsForParsed(parsed
|
|
1639
|
+
const { waypoints, recommended } = buildWaypointsForParsed(parsed, {
|
|
1640
|
+
composeText: options.composeText
|
|
1641
|
+
});
|
|
1222
1642
|
return {
|
|
1223
1643
|
parsed,
|
|
1224
1644
|
source,
|
|
@@ -1260,7 +1680,8 @@ async function resolveUrl(url, options = {}) {
|
|
|
1260
1680
|
}
|
|
1261
1681
|
const { waypoints, recommended } = buildWaypointsForParsed(parsed, {
|
|
1262
1682
|
did,
|
|
1263
|
-
excludeSourceId: source
|
|
1683
|
+
excludeSourceId: source,
|
|
1684
|
+
composeText: options.composeText
|
|
1264
1685
|
});
|
|
1265
1686
|
return {
|
|
1266
1687
|
parsed: { ...parsed, did },
|
|
@@ -1283,6 +1704,7 @@ async function resolveViaApi(input, options = {}) {
|
|
|
1283
1704
|
throw new Error("resolveViaApi requires either `url` or `atUri`");
|
|
1284
1705
|
}
|
|
1285
1706
|
if (input.headDetect === false) params.set("headDetect", "false");
|
|
1707
|
+
if (input.composeText) params.set("composeText", input.composeText);
|
|
1286
1708
|
const res = await fetchImpl(`${endpoint}?${params.toString()}`, {
|
|
1287
1709
|
method: "GET",
|
|
1288
1710
|
headers: { Accept: "application/json" },
|
|
@@ -1322,6 +1744,226 @@ async function detectAtUriInHead(url, timeoutMs) {
|
|
|
1322
1744
|
}
|
|
1323
1745
|
}
|
|
1324
1746
|
|
|
1325
|
-
|
|
1747
|
+
// src/universalLinks.ts
|
|
1748
|
+
var UNIVERSAL_LINK_ORIGIN = "https://aturi.to";
|
|
1749
|
+
var OEMBED_PATH = "/api/oembed";
|
|
1750
|
+
var OEMBED_COLLECTION = "app.bsky.feed.post";
|
|
1751
|
+
var OEMBED_LINK_TYPE = "application/json+oembed";
|
|
1752
|
+
function buildUniversalLink(input, options = {}) {
|
|
1753
|
+
const parsed = parseTarget(input);
|
|
1754
|
+
if (!parsed) return null;
|
|
1755
|
+
const origin = normalizeOrigin(options.origin);
|
|
1756
|
+
const did = options.did ?? parsed.did;
|
|
1757
|
+
const { collection, rkey } = parsed;
|
|
1758
|
+
const identifier = options.preferDid && did ? did : parsed.handle;
|
|
1759
|
+
let path;
|
|
1760
|
+
if (collection && rkey) {
|
|
1761
|
+
if (collection === "app.bsky.feed.post") {
|
|
1762
|
+
path = `/profile/${seg(identifier)}/post/${seg(rkey)}`;
|
|
1763
|
+
} else if (collection === "app.bsky.graph.list") {
|
|
1764
|
+
path = `/profile/${seg(identifier)}/lists/${seg(rkey)}`;
|
|
1765
|
+
} else {
|
|
1766
|
+
path = `/profile/${seg(did || parsed.handle)}/${seg(collection)}/${seg(rkey)}`;
|
|
1767
|
+
}
|
|
1768
|
+
} else {
|
|
1769
|
+
path = `/profile/${seg(identifier)}`;
|
|
1770
|
+
}
|
|
1771
|
+
return `${origin}${path}${queryString(options.params)}`;
|
|
1772
|
+
}
|
|
1773
|
+
function parseUniversalLink(url, options = {}) {
|
|
1774
|
+
let target;
|
|
1775
|
+
try {
|
|
1776
|
+
target = typeof url === "string" ? new URL(url.trim()) : url;
|
|
1777
|
+
} catch {
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
if (!/^https?:$/.test(target.protocol)) return null;
|
|
1781
|
+
let expectedHost;
|
|
1782
|
+
try {
|
|
1783
|
+
expectedHost = bareHost(new URL(normalizeOrigin(options.origin)).hostname);
|
|
1784
|
+
} catch {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
if (bareHost(target.hostname) !== expectedHost) return null;
|
|
1788
|
+
const probe = new URL(target.toString());
|
|
1789
|
+
probe.protocol = "https:";
|
|
1790
|
+
probe.hostname = "aturi.to";
|
|
1791
|
+
probe.port = "";
|
|
1792
|
+
const match = matchSupportedUrl(probe);
|
|
1793
|
+
if (match && (match.source === "aturi" || match.source === "aturiExplore")) {
|
|
1794
|
+
return normalizeParsed(match.parsed);
|
|
1795
|
+
}
|
|
1796
|
+
return parseEmbeddedAtUri(probe.pathname) ?? parsePath(probe.pathname);
|
|
1797
|
+
}
|
|
1798
|
+
function isUniversalLink(url, options = {}) {
|
|
1799
|
+
return parseUniversalLink(url, options) !== null;
|
|
1800
|
+
}
|
|
1801
|
+
function describeUniversalLink(input, options = {}) {
|
|
1802
|
+
const parsed = parseTarget(input);
|
|
1803
|
+
if (!parsed) return null;
|
|
1804
|
+
const url = buildUniversalLink(parsed, options);
|
|
1805
|
+
if (!url) return null;
|
|
1806
|
+
const did = options.did ?? parsed.did ?? null;
|
|
1807
|
+
const collection = parsed.collection ?? null;
|
|
1808
|
+
const rkey = parsed.rkey ?? null;
|
|
1809
|
+
const authority = did ?? parsed.handle;
|
|
1810
|
+
const atUri = collection && rkey ? `at://${authority}/${collection}/${rkey}` : `at://${authority}`;
|
|
1811
|
+
const label = describeTarget(parsed);
|
|
1812
|
+
return {
|
|
1813
|
+
url,
|
|
1814
|
+
atUri,
|
|
1815
|
+
type: parsed.type,
|
|
1816
|
+
handle: parsed.handle,
|
|
1817
|
+
did,
|
|
1818
|
+
collection,
|
|
1819
|
+
rkey,
|
|
1820
|
+
label,
|
|
1821
|
+
share: {
|
|
1822
|
+
title: options.title ?? label,
|
|
1823
|
+
text: options.text ?? label,
|
|
1824
|
+
url
|
|
1825
|
+
},
|
|
1826
|
+
snippets: {
|
|
1827
|
+
url,
|
|
1828
|
+
atUri,
|
|
1829
|
+
markdown: `[${label}](${url})`,
|
|
1830
|
+
html: `<a href="${escapeHtml(url)}">${escapeHtml(label)}</a>`
|
|
1831
|
+
},
|
|
1832
|
+
oembedUrl: buildOEmbedUrl(url, collection, options.origin)
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function buildUniversalLinkTags(input, options = {}) {
|
|
1836
|
+
const described = describeUniversalLink(input, options);
|
|
1837
|
+
if (!described) return null;
|
|
1838
|
+
const meta = [
|
|
1839
|
+
{ name: "at:canonical", content: described.atUri }
|
|
1840
|
+
];
|
|
1841
|
+
if (described.collection && described.rkey) {
|
|
1842
|
+
meta.push({
|
|
1843
|
+
name: "at:author",
|
|
1844
|
+
content: `at://${described.did ?? described.handle}`
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
const link = [
|
|
1848
|
+
{ rel: "alternate", href: described.atUri }
|
|
1849
|
+
];
|
|
1850
|
+
if (described.oembedUrl) {
|
|
1851
|
+
link.push({
|
|
1852
|
+
rel: "alternate",
|
|
1853
|
+
type: OEMBED_LINK_TYPE,
|
|
1854
|
+
href: described.oembedUrl
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
const html = [
|
|
1858
|
+
...meta.map(
|
|
1859
|
+
(tag) => `<meta name="${escapeHtml(tag.name)}" content="${escapeHtml(tag.content)}" />`
|
|
1860
|
+
),
|
|
1861
|
+
...link.map((tag) => {
|
|
1862
|
+
const type = tag.type ? ` type="${escapeHtml(tag.type)}"` : "";
|
|
1863
|
+
return `<link rel="${escapeHtml(tag.rel)}"${type} href="${escapeHtml(tag.href)}" />`;
|
|
1864
|
+
})
|
|
1865
|
+
].join("\n");
|
|
1866
|
+
return { meta, link, html };
|
|
1867
|
+
}
|
|
1868
|
+
function parseTarget(input) {
|
|
1869
|
+
if (typeof input !== "string") {
|
|
1870
|
+
return input && input.handle ? normalizeParsed(input) : null;
|
|
1871
|
+
}
|
|
1872
|
+
const trimmed = input.trim();
|
|
1873
|
+
if (!trimmed) return null;
|
|
1874
|
+
if (trimmed.toLowerCase().startsWith("at://")) {
|
|
1875
|
+
const parsed = parseAtUri(trimmed)?.parsed;
|
|
1876
|
+
return parsed ? normalizeParsed(parsed) : null;
|
|
1877
|
+
}
|
|
1878
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
1879
|
+
let target;
|
|
1880
|
+
try {
|
|
1881
|
+
target = new URL(trimmed);
|
|
1882
|
+
} catch {
|
|
1883
|
+
return null;
|
|
1884
|
+
}
|
|
1885
|
+
const embedded = parseEmbeddedAtUri(target.pathname);
|
|
1886
|
+
if (embedded) return embedded;
|
|
1887
|
+
const matched = matchSupportedUrl(target)?.parsed;
|
|
1888
|
+
return matched ? normalizeParsed(matched) : null;
|
|
1889
|
+
}
|
|
1890
|
+
return parsePath(trimmed.startsWith("@") ? trimmed.slice(1) : trimmed);
|
|
1891
|
+
}
|
|
1892
|
+
function parsePath(path) {
|
|
1893
|
+
const segments = path.split("/").filter(Boolean);
|
|
1894
|
+
const handle = decodeSegment(segments[0] ?? "");
|
|
1895
|
+
if (!handle) return null;
|
|
1896
|
+
if (!handle.startsWith("did:") && !handle.includes(".")) return null;
|
|
1897
|
+
const parsed = parseAtUri(`at://${segments.join("/")}`)?.parsed;
|
|
1898
|
+
if (!parsed) return null;
|
|
1899
|
+
const collection = parsed.collection ? decodeSegment(parsed.collection) : void 0;
|
|
1900
|
+
const rkey = parsed.rkey ? decodeSegment(parsed.rkey) : void 0;
|
|
1901
|
+
return {
|
|
1902
|
+
...parsed,
|
|
1903
|
+
handle,
|
|
1904
|
+
did: handle.startsWith("did:") ? handle : void 0,
|
|
1905
|
+
collection,
|
|
1906
|
+
rkey,
|
|
1907
|
+
uri: collection && rkey ? `at://${handle}/${collection}/${rkey}` : `at://${handle}`
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
function normalizeParsed(parsed) {
|
|
1911
|
+
if (!parsed.handle.startsWith("@")) return parsed;
|
|
1912
|
+
const handle = parsed.handle.slice(1);
|
|
1913
|
+
if (!handle) return parsed;
|
|
1914
|
+
return {
|
|
1915
|
+
...parsed,
|
|
1916
|
+
handle,
|
|
1917
|
+
uri: parsed.collection && parsed.rkey ? `at://${handle}/${parsed.collection}/${parsed.rkey}` : `at://${handle}`
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function parseEmbeddedAtUri(pathname) {
|
|
1921
|
+
const match = /^\/at:\/{1,2}(.+)$/i.exec(pathname);
|
|
1922
|
+
if (!match) return null;
|
|
1923
|
+
return parsePath(match[1]);
|
|
1924
|
+
}
|
|
1925
|
+
function buildOEmbedUrl(url, collection, origin) {
|
|
1926
|
+
if (collection !== OEMBED_COLLECTION) return null;
|
|
1927
|
+
return `${normalizeOrigin(origin)}${OEMBED_PATH}?url=${encodeURIComponent(url)}`;
|
|
1928
|
+
}
|
|
1929
|
+
function describeTarget(parsed) {
|
|
1930
|
+
const who = parsed.handle.startsWith("did:") ? parsed.handle : `@${parsed.handle}`;
|
|
1931
|
+
if (parsed.type === "post") return `Post by ${who}`;
|
|
1932
|
+
if (parsed.type === "list") return `List by ${who}`;
|
|
1933
|
+
if (parsed.collection) return `${parsed.collection} record by ${who}`;
|
|
1934
|
+
return who;
|
|
1935
|
+
}
|
|
1936
|
+
function normalizeOrigin(origin) {
|
|
1937
|
+
if (!origin) return UNIVERSAL_LINK_ORIGIN;
|
|
1938
|
+
return origin.trim().replace(/\/+$/, "") || UNIVERSAL_LINK_ORIGIN;
|
|
1939
|
+
}
|
|
1940
|
+
function bareHost(hostname) {
|
|
1941
|
+
return hostname.toLowerCase().replace(/^www\./, "");
|
|
1942
|
+
}
|
|
1943
|
+
function seg(value) {
|
|
1944
|
+
return encodeURIComponent(value).replace(/%3A/gi, ":");
|
|
1945
|
+
}
|
|
1946
|
+
function decodeSegment(value) {
|
|
1947
|
+
try {
|
|
1948
|
+
return decodeURIComponent(value);
|
|
1949
|
+
} catch {
|
|
1950
|
+
return value;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function queryString(params) {
|
|
1954
|
+
if (!params) return "";
|
|
1955
|
+
const search = new URLSearchParams();
|
|
1956
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1957
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
1958
|
+
search.set(key, String(value));
|
|
1959
|
+
}
|
|
1960
|
+
const query = search.toString();
|
|
1961
|
+
return query ? `?${query}` : "";
|
|
1962
|
+
}
|
|
1963
|
+
function escapeHtml(value) {
|
|
1964
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
export { CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, COMPOSE_INTENT_TEXT_PLACEHOLDER, DID_REQUIRED_WAYPOINTS, OEMBED_LINK_TYPE, SUPPORTED_HOSTS, UNIVERSAL_LINK_ORIGIN, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, buildUniversalLink, buildUniversalLinkTags, buildWaypointsForParsed, describeComposeIntent, describeUniversalLink, getCategorizedWaypointsData, getComposeIntentAppUrl, getComposeIntentTemplate, getComposeIntentUrl, getComposeIntentWaypoints, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, isSupportedHost, isUniversalLink, matchSupportedUrl, parseAtUri, parseURI, parseUniversalLink, resolveAtUri, resolveHandle, resolveHandleStatus, resolveUrl, resolveViaApi, supportsComposeIntent, waypointActivity };
|
|
1326
1968
|
//# sourceMappingURL=index.js.map
|
|
1327
1969
|
//# sourceMappingURL=index.js.map
|