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