@aturi.to/waypoints 0.1.2 → 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 +147 -3
- package/dist/index.cjs +383 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +265 -4
- package/dist/index.d.ts +265 -4
- package/dist/index.js +370 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -77,6 +77,9 @@ var COMPAT_FAMILY_ORDER = [
|
|
|
77
77
|
"blento",
|
|
78
78
|
"atproto-explorer"
|
|
79
79
|
];
|
|
80
|
+
function socialAppComposeIntent(origin) {
|
|
81
|
+
return { url: `${origin}/intent/compose`, textParam: "text" };
|
|
82
|
+
}
|
|
80
83
|
var WAYPOINT_DESTINATIONS_DATA = {
|
|
81
84
|
aturi: {
|
|
82
85
|
id: "aturi",
|
|
@@ -136,6 +139,11 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
136
139
|
},
|
|
137
140
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
138
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"),
|
|
139
147
|
redirectCompat: ["bluesky-social", "standard-site"],
|
|
140
148
|
expectedCollections: ["app.bsky.", "net.anisota."]
|
|
141
149
|
},
|
|
@@ -152,12 +160,19 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
152
160
|
if (collection === "app.bsky.feed.post") {
|
|
153
161
|
return `https://bsky.app/profile/${handle}/post/${rkey}`;
|
|
154
162
|
}
|
|
163
|
+
if (collection === "app.bsky.graph.list") {
|
|
164
|
+
return `https://bsky.app/profile/${handle}/lists/${rkey}`;
|
|
165
|
+
}
|
|
155
166
|
return `https://bsky.app/profile/${handle}`;
|
|
156
167
|
}
|
|
157
168
|
return `https://bsky.app/profile/${handle}`;
|
|
158
169
|
},
|
|
159
170
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
160
171
|
category: "blueskyClients",
|
|
172
|
+
composeIntent: {
|
|
173
|
+
...socialAppComposeIntent("https://bsky.app"),
|
|
174
|
+
appUrl: "bluesky://intent/compose"
|
|
175
|
+
},
|
|
161
176
|
redirectCompat: ["bluesky-social"],
|
|
162
177
|
expectedCollections: ["app.bsky."]
|
|
163
178
|
},
|
|
@@ -174,12 +189,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
174
189
|
if (collection === "app.bsky.feed.post") {
|
|
175
190
|
return `https://blacksky.community/profile/${handle}/post/${rkey}`;
|
|
176
191
|
}
|
|
192
|
+
if (collection === "app.bsky.graph.list") {
|
|
193
|
+
return `https://blacksky.community/profile/${handle}/lists/${rkey}`;
|
|
194
|
+
}
|
|
177
195
|
return `https://blacksky.community/profile/${handle}`;
|
|
178
196
|
}
|
|
179
197
|
return `https://blacksky.community/profile/${handle}`;
|
|
180
198
|
},
|
|
181
199
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
182
200
|
category: "blueskyForks",
|
|
201
|
+
composeIntent: socialAppComposeIntent("https://blacksky.community"),
|
|
183
202
|
redirectCompat: ["bluesky-social"],
|
|
184
203
|
expectedCollections: ["app.bsky."]
|
|
185
204
|
},
|
|
@@ -188,7 +207,6 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
188
207
|
name: "Red Dwarf",
|
|
189
208
|
description: (collection) => {
|
|
190
209
|
if (collection === "app.bsky.feed.post") return "View post on reddwarf.app";
|
|
191
|
-
if (collection === "app.bsky.graph.list") return "View list on reddwarf.app";
|
|
192
210
|
return "View profile on reddwarf.app";
|
|
193
211
|
},
|
|
194
212
|
getUrl: (handle, collection, rkey) => {
|
|
@@ -200,7 +218,10 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
200
218
|
}
|
|
201
219
|
return `https://reddwarf.app/profile/${handle}`;
|
|
202
220
|
},
|
|
203
|
-
|
|
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"],
|
|
204
225
|
category: "blueskyClients",
|
|
205
226
|
redirectCompat: ["bluesky-social"],
|
|
206
227
|
expectedCollections: ["app.bsky."]
|
|
@@ -227,6 +248,33 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
227
248
|
},
|
|
228
249
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
229
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",
|
|
230
278
|
redirectCompat: ["bluesky-social"],
|
|
231
279
|
expectedCollections: ["app.bsky."]
|
|
232
280
|
},
|
|
@@ -327,12 +375,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
327
375
|
if (collection === "app.bsky.feed.post") {
|
|
328
376
|
return `https://witchsky.app/profile/${handle}/post/${rkey}`;
|
|
329
377
|
}
|
|
378
|
+
if (collection === "app.bsky.graph.list") {
|
|
379
|
+
return `https://witchsky.app/profile/${handle}/lists/${rkey}`;
|
|
380
|
+
}
|
|
330
381
|
return `https://witchsky.app/profile/${handle}`;
|
|
331
382
|
}
|
|
332
383
|
return `https://witchsky.app/profile/${handle}`;
|
|
333
384
|
},
|
|
334
385
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
335
386
|
category: "blueskyForks",
|
|
387
|
+
composeIntent: socialAppComposeIntent("https://witchsky.app"),
|
|
336
388
|
redirectCompat: ["bluesky-social"],
|
|
337
389
|
expectedCollections: ["app.bsky."]
|
|
338
390
|
},
|
|
@@ -349,12 +401,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
349
401
|
if (collection === "app.bsky.feed.post") {
|
|
350
402
|
return `https://deer.social/profile/${handle}/post/${rkey}`;
|
|
351
403
|
}
|
|
404
|
+
if (collection === "app.bsky.graph.list") {
|
|
405
|
+
return `https://deer.social/profile/${handle}/lists/${rkey}`;
|
|
406
|
+
}
|
|
352
407
|
return `https://deer.social/profile/${handle}`;
|
|
353
408
|
}
|
|
354
409
|
return `https://deer.social/profile/${handle}`;
|
|
355
410
|
},
|
|
356
411
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
357
412
|
category: "blueskyForks",
|
|
413
|
+
composeIntent: socialAppComposeIntent("https://deer.social"),
|
|
358
414
|
redirectCompat: ["bluesky-social"],
|
|
359
415
|
expectedCollections: ["app.bsky."]
|
|
360
416
|
},
|
|
@@ -371,12 +427,42 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
371
427
|
if (collection === "app.bsky.feed.post") {
|
|
372
428
|
return `https://mu.social/profile/${handle}/post/${rkey}`;
|
|
373
429
|
}
|
|
430
|
+
if (collection === "app.bsky.graph.list") {
|
|
431
|
+
return `https://mu.social/profile/${handle}/lists/${rkey}`;
|
|
432
|
+
}
|
|
374
433
|
return `https://mu.social/profile/${handle}`;
|
|
375
434
|
}
|
|
376
435
|
return `https://mu.social/profile/${handle}`;
|
|
377
436
|
},
|
|
378
437
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
379
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}`;
|
|
462
|
+
},
|
|
463
|
+
supportedTypes: ["post", "profile", "list", "record"],
|
|
464
|
+
category: "blueskyForks",
|
|
465
|
+
composeIntent: socialAppComposeIntent("https://northsky.app"),
|
|
380
466
|
redirectCompat: ["bluesky-social"],
|
|
381
467
|
expectedCollections: ["app.bsky."]
|
|
382
468
|
},
|
|
@@ -655,7 +741,9 @@ var WAYPOINT_ORDER = [
|
|
|
655
741
|
"taproot",
|
|
656
742
|
"witchsky",
|
|
657
743
|
"mu",
|
|
658
|
-
"deer"
|
|
744
|
+
"deer",
|
|
745
|
+
"lea",
|
|
746
|
+
"northsky"
|
|
659
747
|
];
|
|
660
748
|
function getWaypointDataForType(type) {
|
|
661
749
|
return WAYPOINT_ORDER.map((id) => WAYPOINT_DESTINATIONS_DATA[id]).filter((waypoint) => waypoint.supportedTypes.includes(type));
|
|
@@ -822,6 +910,47 @@ function waypointActivity(waypoint, repoCollections) {
|
|
|
822
910
|
}
|
|
823
911
|
return "absent";
|
|
824
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
|
+
}
|
|
825
954
|
|
|
826
955
|
// src/upstreamFetch.ts
|
|
827
956
|
var RETRY_DELAY_MS = 250;
|
|
@@ -920,8 +1049,10 @@ var BLUESKY_FAMILY = [
|
|
|
920
1049
|
{ source: "blacksky", hosts: ["blacksky.community"] },
|
|
921
1050
|
{ source: "reddwarf", hosts: ["reddwarf.app"] },
|
|
922
1051
|
{ source: "impro", hosts: ["impro.social"] },
|
|
1052
|
+
{ source: "lea", hosts: ["lea.ac"] },
|
|
923
1053
|
{ source: "witchsky", hosts: ["witchsky.app"] },
|
|
924
1054
|
{ source: "deer", hosts: ["deer.social"] },
|
|
1055
|
+
{ source: "northsky", hosts: ["northsky.app"] },
|
|
925
1056
|
{ source: "mu", hosts: ["mu.social"] },
|
|
926
1057
|
{ source: "anisota", hosts: ["anisota.net"], matchSubdomains: true }
|
|
927
1058
|
];
|
|
@@ -1341,7 +1472,7 @@ function matchTaproot(host, pathname) {
|
|
|
1341
1472
|
}
|
|
1342
1473
|
function matchAturi(host, parts) {
|
|
1343
1474
|
if (host !== "aturi.to") return null;
|
|
1344
|
-
const isIdentifier = (
|
|
1475
|
+
const isIdentifier = (seg2) => !!seg2 && (seg2.startsWith("did:") || seg2.includes("."));
|
|
1345
1476
|
if (parts[0] === "explore" && isIdentifier(parts[1])) {
|
|
1346
1477
|
const handle = parts[1];
|
|
1347
1478
|
const did = handle.startsWith("did:") ? handle : void 0;
|
|
@@ -1490,18 +1621,26 @@ function buildWaypointsForParsed(parsed, options = {}) {
|
|
|
1490
1621
|
if (DID_REQUIRED_WAYPOINTS.has(w.id) && !did) return null;
|
|
1491
1622
|
const url = w.getUrl(parsed.handle, parsed.collection, parsed.rkey, did);
|
|
1492
1623
|
if (!url) return null;
|
|
1493
|
-
return {
|
|
1624
|
+
return {
|
|
1625
|
+
id: w.id,
|
|
1626
|
+
name: w.name,
|
|
1627
|
+
category: w.category,
|
|
1628
|
+
url,
|
|
1629
|
+
composeIntent: describeComposeIntent(w, options.composeText)
|
|
1630
|
+
};
|
|
1494
1631
|
}).filter((w) => !!w);
|
|
1495
1632
|
const recommendedRaw = getRecommendedWaypointsData(type, parsed.collection);
|
|
1496
1633
|
const availableIds = new Set(waypoints.map((w) => w.id));
|
|
1497
1634
|
const ids = recommendedRaw.waypoints.map((w) => w.id).filter((id) => id !== exclude && availableIds.has(id));
|
|
1498
1635
|
return { waypoints, recommended: { ids, label: recommendedRaw.label } };
|
|
1499
1636
|
}
|
|
1500
|
-
function resolveAtUri(uri) {
|
|
1637
|
+
function resolveAtUri(uri, options = {}) {
|
|
1501
1638
|
const match = parseAtUri(uri);
|
|
1502
1639
|
if (!match) return null;
|
|
1503
1640
|
const { parsed, source } = match;
|
|
1504
|
-
const { waypoints, recommended } = buildWaypointsForParsed(parsed
|
|
1641
|
+
const { waypoints, recommended } = buildWaypointsForParsed(parsed, {
|
|
1642
|
+
composeText: options.composeText
|
|
1643
|
+
});
|
|
1505
1644
|
return {
|
|
1506
1645
|
parsed,
|
|
1507
1646
|
source,
|
|
@@ -1543,7 +1682,8 @@ async function resolveUrl(url, options = {}) {
|
|
|
1543
1682
|
}
|
|
1544
1683
|
const { waypoints, recommended } = buildWaypointsForParsed(parsed, {
|
|
1545
1684
|
did,
|
|
1546
|
-
excludeSourceId: source
|
|
1685
|
+
excludeSourceId: source,
|
|
1686
|
+
composeText: options.composeText
|
|
1547
1687
|
});
|
|
1548
1688
|
return {
|
|
1549
1689
|
parsed: { ...parsed, did },
|
|
@@ -1566,6 +1706,7 @@ async function resolveViaApi(input, options = {}) {
|
|
|
1566
1706
|
throw new Error("resolveViaApi requires either `url` or `atUri`");
|
|
1567
1707
|
}
|
|
1568
1708
|
if (input.headDetect === false) params.set("headDetect", "false");
|
|
1709
|
+
if (input.composeText) params.set("composeText", input.composeText);
|
|
1569
1710
|
const res = await fetchImpl(`${endpoint}?${params.toString()}`, {
|
|
1570
1711
|
method: "GET",
|
|
1571
1712
|
headers: { Accept: "application/json" },
|
|
@@ -1605,30 +1746,264 @@ async function detectAtUriInHead(url, timeoutMs) {
|
|
|
1605
1746
|
}
|
|
1606
1747
|
}
|
|
1607
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
|
+
|
|
1608
1969
|
exports.CATEGORY_ORDER = CATEGORY_ORDER;
|
|
1609
1970
|
exports.COMPAT_FAMILIES = COMPAT_FAMILIES;
|
|
1610
1971
|
exports.COMPAT_FAMILY_ORDER = COMPAT_FAMILY_ORDER;
|
|
1972
|
+
exports.COMPOSE_INTENT_TEXT_PLACEHOLDER = COMPOSE_INTENT_TEXT_PLACEHOLDER;
|
|
1611
1973
|
exports.DID_REQUIRED_WAYPOINTS = DID_REQUIRED_WAYPOINTS;
|
|
1974
|
+
exports.OEMBED_LINK_TYPE = OEMBED_LINK_TYPE;
|
|
1612
1975
|
exports.SUPPORTED_HOSTS = SUPPORTED_HOSTS;
|
|
1976
|
+
exports.UNIVERSAL_LINK_ORIGIN = UNIVERSAL_LINK_ORIGIN;
|
|
1613
1977
|
exports.WAYPOINT_CATEGORIES_DATA = WAYPOINT_CATEGORIES_DATA;
|
|
1614
1978
|
exports.WAYPOINT_DESTINATIONS_DATA = WAYPOINT_DESTINATIONS_DATA;
|
|
1615
1979
|
exports.WAYPOINT_ORDER = WAYPOINT_ORDER;
|
|
1980
|
+
exports.buildUniversalLink = buildUniversalLink;
|
|
1981
|
+
exports.buildUniversalLinkTags = buildUniversalLinkTags;
|
|
1616
1982
|
exports.buildWaypointsForParsed = buildWaypointsForParsed;
|
|
1983
|
+
exports.describeComposeIntent = describeComposeIntent;
|
|
1984
|
+
exports.describeUniversalLink = describeUniversalLink;
|
|
1617
1985
|
exports.getCategorizedWaypointsData = getCategorizedWaypointsData;
|
|
1986
|
+
exports.getComposeIntentAppUrl = getComposeIntentAppUrl;
|
|
1987
|
+
exports.getComposeIntentTemplate = getComposeIntentTemplate;
|
|
1988
|
+
exports.getComposeIntentUrl = getComposeIntentUrl;
|
|
1989
|
+
exports.getComposeIntentWaypoints = getComposeIntentWaypoints;
|
|
1618
1990
|
exports.getDisplayName = getDisplayName;
|
|
1619
1991
|
exports.getFeaturedWaypointData = getFeaturedWaypointData;
|
|
1620
1992
|
exports.getRecommendedWaypointsData = getRecommendedWaypointsData;
|
|
1621
1993
|
exports.getWaypointCountData = getWaypointCountData;
|
|
1622
1994
|
exports.getWaypointDataForType = getWaypointDataForType;
|
|
1623
1995
|
exports.isSupportedHost = isSupportedHost;
|
|
1996
|
+
exports.isUniversalLink = isUniversalLink;
|
|
1624
1997
|
exports.matchSupportedUrl = matchSupportedUrl;
|
|
1625
1998
|
exports.parseAtUri = parseAtUri;
|
|
1626
1999
|
exports.parseURI = parseURI;
|
|
2000
|
+
exports.parseUniversalLink = parseUniversalLink;
|
|
1627
2001
|
exports.resolveAtUri = resolveAtUri;
|
|
1628
2002
|
exports.resolveHandle = resolveHandle;
|
|
1629
2003
|
exports.resolveHandleStatus = resolveHandleStatus;
|
|
1630
2004
|
exports.resolveUrl = resolveUrl;
|
|
1631
2005
|
exports.resolveViaApi = resolveViaApi;
|
|
2006
|
+
exports.supportsComposeIntent = supportsComposeIntent;
|
|
1632
2007
|
exports.waypointActivity = waypointActivity;
|
|
1633
2008
|
//# sourceMappingURL=index.cjs.map
|
|
1634
2009
|
//# sourceMappingURL=index.cjs.map
|