@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.js
CHANGED
|
@@ -75,6 +75,9 @@ var COMPAT_FAMILY_ORDER = [
|
|
|
75
75
|
"blento",
|
|
76
76
|
"atproto-explorer"
|
|
77
77
|
];
|
|
78
|
+
function socialAppComposeIntent(origin) {
|
|
79
|
+
return { url: `${origin}/intent/compose`, textParam: "text" };
|
|
80
|
+
}
|
|
78
81
|
var WAYPOINT_DESTINATIONS_DATA = {
|
|
79
82
|
aturi: {
|
|
80
83
|
id: "aturi",
|
|
@@ -134,6 +137,11 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
134
137
|
},
|
|
135
138
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
136
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"),
|
|
137
145
|
redirectCompat: ["bluesky-social", "standard-site"],
|
|
138
146
|
expectedCollections: ["app.bsky.", "net.anisota."]
|
|
139
147
|
},
|
|
@@ -150,12 +158,19 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
150
158
|
if (collection === "app.bsky.feed.post") {
|
|
151
159
|
return `https://bsky.app/profile/${handle}/post/${rkey}`;
|
|
152
160
|
}
|
|
161
|
+
if (collection === "app.bsky.graph.list") {
|
|
162
|
+
return `https://bsky.app/profile/${handle}/lists/${rkey}`;
|
|
163
|
+
}
|
|
153
164
|
return `https://bsky.app/profile/${handle}`;
|
|
154
165
|
}
|
|
155
166
|
return `https://bsky.app/profile/${handle}`;
|
|
156
167
|
},
|
|
157
168
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
158
169
|
category: "blueskyClients",
|
|
170
|
+
composeIntent: {
|
|
171
|
+
...socialAppComposeIntent("https://bsky.app"),
|
|
172
|
+
appUrl: "bluesky://intent/compose"
|
|
173
|
+
},
|
|
159
174
|
redirectCompat: ["bluesky-social"],
|
|
160
175
|
expectedCollections: ["app.bsky."]
|
|
161
176
|
},
|
|
@@ -172,12 +187,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
172
187
|
if (collection === "app.bsky.feed.post") {
|
|
173
188
|
return `https://blacksky.community/profile/${handle}/post/${rkey}`;
|
|
174
189
|
}
|
|
190
|
+
if (collection === "app.bsky.graph.list") {
|
|
191
|
+
return `https://blacksky.community/profile/${handle}/lists/${rkey}`;
|
|
192
|
+
}
|
|
175
193
|
return `https://blacksky.community/profile/${handle}`;
|
|
176
194
|
}
|
|
177
195
|
return `https://blacksky.community/profile/${handle}`;
|
|
178
196
|
},
|
|
179
197
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
180
198
|
category: "blueskyForks",
|
|
199
|
+
composeIntent: socialAppComposeIntent("https://blacksky.community"),
|
|
181
200
|
redirectCompat: ["bluesky-social"],
|
|
182
201
|
expectedCollections: ["app.bsky."]
|
|
183
202
|
},
|
|
@@ -186,7 +205,6 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
186
205
|
name: "Red Dwarf",
|
|
187
206
|
description: (collection) => {
|
|
188
207
|
if (collection === "app.bsky.feed.post") return "View post on reddwarf.app";
|
|
189
|
-
if (collection === "app.bsky.graph.list") return "View list on reddwarf.app";
|
|
190
208
|
return "View profile on reddwarf.app";
|
|
191
209
|
},
|
|
192
210
|
getUrl: (handle, collection, rkey) => {
|
|
@@ -198,7 +216,10 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
198
216
|
}
|
|
199
217
|
return `https://reddwarf.app/profile/${handle}`;
|
|
200
218
|
},
|
|
201
|
-
|
|
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"],
|
|
202
223
|
category: "blueskyClients",
|
|
203
224
|
redirectCompat: ["bluesky-social"],
|
|
204
225
|
expectedCollections: ["app.bsky."]
|
|
@@ -225,6 +246,33 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
225
246
|
},
|
|
226
247
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
227
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",
|
|
228
276
|
redirectCompat: ["bluesky-social"],
|
|
229
277
|
expectedCollections: ["app.bsky."]
|
|
230
278
|
},
|
|
@@ -325,12 +373,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
325
373
|
if (collection === "app.bsky.feed.post") {
|
|
326
374
|
return `https://witchsky.app/profile/${handle}/post/${rkey}`;
|
|
327
375
|
}
|
|
376
|
+
if (collection === "app.bsky.graph.list") {
|
|
377
|
+
return `https://witchsky.app/profile/${handle}/lists/${rkey}`;
|
|
378
|
+
}
|
|
328
379
|
return `https://witchsky.app/profile/${handle}`;
|
|
329
380
|
}
|
|
330
381
|
return `https://witchsky.app/profile/${handle}`;
|
|
331
382
|
},
|
|
332
383
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
333
384
|
category: "blueskyForks",
|
|
385
|
+
composeIntent: socialAppComposeIntent("https://witchsky.app"),
|
|
334
386
|
redirectCompat: ["bluesky-social"],
|
|
335
387
|
expectedCollections: ["app.bsky."]
|
|
336
388
|
},
|
|
@@ -347,12 +399,16 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
347
399
|
if (collection === "app.bsky.feed.post") {
|
|
348
400
|
return `https://deer.social/profile/${handle}/post/${rkey}`;
|
|
349
401
|
}
|
|
402
|
+
if (collection === "app.bsky.graph.list") {
|
|
403
|
+
return `https://deer.social/profile/${handle}/lists/${rkey}`;
|
|
404
|
+
}
|
|
350
405
|
return `https://deer.social/profile/${handle}`;
|
|
351
406
|
}
|
|
352
407
|
return `https://deer.social/profile/${handle}`;
|
|
353
408
|
},
|
|
354
409
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
355
410
|
category: "blueskyForks",
|
|
411
|
+
composeIntent: socialAppComposeIntent("https://deer.social"),
|
|
356
412
|
redirectCompat: ["bluesky-social"],
|
|
357
413
|
expectedCollections: ["app.bsky."]
|
|
358
414
|
},
|
|
@@ -369,12 +425,42 @@ var WAYPOINT_DESTINATIONS_DATA = {
|
|
|
369
425
|
if (collection === "app.bsky.feed.post") {
|
|
370
426
|
return `https://mu.social/profile/${handle}/post/${rkey}`;
|
|
371
427
|
}
|
|
428
|
+
if (collection === "app.bsky.graph.list") {
|
|
429
|
+
return `https://mu.social/profile/${handle}/lists/${rkey}`;
|
|
430
|
+
}
|
|
372
431
|
return `https://mu.social/profile/${handle}`;
|
|
373
432
|
}
|
|
374
433
|
return `https://mu.social/profile/${handle}`;
|
|
375
434
|
},
|
|
376
435
|
supportedTypes: ["post", "profile", "list", "record"],
|
|
377
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}`;
|
|
460
|
+
},
|
|
461
|
+
supportedTypes: ["post", "profile", "list", "record"],
|
|
462
|
+
category: "blueskyForks",
|
|
463
|
+
composeIntent: socialAppComposeIntent("https://northsky.app"),
|
|
378
464
|
redirectCompat: ["bluesky-social"],
|
|
379
465
|
expectedCollections: ["app.bsky."]
|
|
380
466
|
},
|
|
@@ -653,7 +739,9 @@ var WAYPOINT_ORDER = [
|
|
|
653
739
|
"taproot",
|
|
654
740
|
"witchsky",
|
|
655
741
|
"mu",
|
|
656
|
-
"deer"
|
|
742
|
+
"deer",
|
|
743
|
+
"lea",
|
|
744
|
+
"northsky"
|
|
657
745
|
];
|
|
658
746
|
function getWaypointDataForType(type) {
|
|
659
747
|
return WAYPOINT_ORDER.map((id) => WAYPOINT_DESTINATIONS_DATA[id]).filter((waypoint) => waypoint.supportedTypes.includes(type));
|
|
@@ -820,6 +908,47 @@ function waypointActivity(waypoint, repoCollections) {
|
|
|
820
908
|
}
|
|
821
909
|
return "absent";
|
|
822
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
|
+
}
|
|
823
952
|
|
|
824
953
|
// src/upstreamFetch.ts
|
|
825
954
|
var RETRY_DELAY_MS = 250;
|
|
@@ -918,8 +1047,10 @@ var BLUESKY_FAMILY = [
|
|
|
918
1047
|
{ source: "blacksky", hosts: ["blacksky.community"] },
|
|
919
1048
|
{ source: "reddwarf", hosts: ["reddwarf.app"] },
|
|
920
1049
|
{ source: "impro", hosts: ["impro.social"] },
|
|
1050
|
+
{ source: "lea", hosts: ["lea.ac"] },
|
|
921
1051
|
{ source: "witchsky", hosts: ["witchsky.app"] },
|
|
922
1052
|
{ source: "deer", hosts: ["deer.social"] },
|
|
1053
|
+
{ source: "northsky", hosts: ["northsky.app"] },
|
|
923
1054
|
{ source: "mu", hosts: ["mu.social"] },
|
|
924
1055
|
{ source: "anisota", hosts: ["anisota.net"], matchSubdomains: true }
|
|
925
1056
|
];
|
|
@@ -1339,7 +1470,7 @@ function matchTaproot(host, pathname) {
|
|
|
1339
1470
|
}
|
|
1340
1471
|
function matchAturi(host, parts) {
|
|
1341
1472
|
if (host !== "aturi.to") return null;
|
|
1342
|
-
const isIdentifier = (
|
|
1473
|
+
const isIdentifier = (seg2) => !!seg2 && (seg2.startsWith("did:") || seg2.includes("."));
|
|
1343
1474
|
if (parts[0] === "explore" && isIdentifier(parts[1])) {
|
|
1344
1475
|
const handle = parts[1];
|
|
1345
1476
|
const did = handle.startsWith("did:") ? handle : void 0;
|
|
@@ -1488,18 +1619,26 @@ function buildWaypointsForParsed(parsed, options = {}) {
|
|
|
1488
1619
|
if (DID_REQUIRED_WAYPOINTS.has(w.id) && !did) return null;
|
|
1489
1620
|
const url = w.getUrl(parsed.handle, parsed.collection, parsed.rkey, did);
|
|
1490
1621
|
if (!url) return null;
|
|
1491
|
-
return {
|
|
1622
|
+
return {
|
|
1623
|
+
id: w.id,
|
|
1624
|
+
name: w.name,
|
|
1625
|
+
category: w.category,
|
|
1626
|
+
url,
|
|
1627
|
+
composeIntent: describeComposeIntent(w, options.composeText)
|
|
1628
|
+
};
|
|
1492
1629
|
}).filter((w) => !!w);
|
|
1493
1630
|
const recommendedRaw = getRecommendedWaypointsData(type, parsed.collection);
|
|
1494
1631
|
const availableIds = new Set(waypoints.map((w) => w.id));
|
|
1495
1632
|
const ids = recommendedRaw.waypoints.map((w) => w.id).filter((id) => id !== exclude && availableIds.has(id));
|
|
1496
1633
|
return { waypoints, recommended: { ids, label: recommendedRaw.label } };
|
|
1497
1634
|
}
|
|
1498
|
-
function resolveAtUri(uri) {
|
|
1635
|
+
function resolveAtUri(uri, options = {}) {
|
|
1499
1636
|
const match = parseAtUri(uri);
|
|
1500
1637
|
if (!match) return null;
|
|
1501
1638
|
const { parsed, source } = match;
|
|
1502
|
-
const { waypoints, recommended } = buildWaypointsForParsed(parsed
|
|
1639
|
+
const { waypoints, recommended } = buildWaypointsForParsed(parsed, {
|
|
1640
|
+
composeText: options.composeText
|
|
1641
|
+
});
|
|
1503
1642
|
return {
|
|
1504
1643
|
parsed,
|
|
1505
1644
|
source,
|
|
@@ -1541,7 +1680,8 @@ async function resolveUrl(url, options = {}) {
|
|
|
1541
1680
|
}
|
|
1542
1681
|
const { waypoints, recommended } = buildWaypointsForParsed(parsed, {
|
|
1543
1682
|
did,
|
|
1544
|
-
excludeSourceId: source
|
|
1683
|
+
excludeSourceId: source,
|
|
1684
|
+
composeText: options.composeText
|
|
1545
1685
|
});
|
|
1546
1686
|
return {
|
|
1547
1687
|
parsed: { ...parsed, did },
|
|
@@ -1564,6 +1704,7 @@ async function resolveViaApi(input, options = {}) {
|
|
|
1564
1704
|
throw new Error("resolveViaApi requires either `url` or `atUri`");
|
|
1565
1705
|
}
|
|
1566
1706
|
if (input.headDetect === false) params.set("headDetect", "false");
|
|
1707
|
+
if (input.composeText) params.set("composeText", input.composeText);
|
|
1567
1708
|
const res = await fetchImpl(`${endpoint}?${params.toString()}`, {
|
|
1568
1709
|
method: "GET",
|
|
1569
1710
|
headers: { Accept: "application/json" },
|
|
@@ -1603,6 +1744,226 @@ async function detectAtUriInHead(url, timeoutMs) {
|
|
|
1603
1744
|
}
|
|
1604
1745
|
}
|
|
1605
1746
|
|
|
1606
|
-
|
|
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 };
|
|
1607
1968
|
//# sourceMappingURL=index.js.map
|
|
1608
1969
|
//# sourceMappingURL=index.js.map
|