@aturi.to/waypoints 0.1.0 → 0.1.2

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/dist/index.d.cts CHANGED
@@ -6,7 +6,7 @@ type WaypointType = 'post' | 'profile' | 'list' | 'record' | 'unknown';
6
6
  * atproto collections). Display groupings live in `WAYPOINT_CATEGORIES_DATA`
7
7
  * and are independent of this.
8
8
  */
9
- type RedirectCompatFamily = 'bluesky-social' | 'standard-site' | 'tangled' | 'margin' | 'grain' | 'pinksky' | 'semble' | 'streamplace' | 'popfeed' | 'sifa' | 'blento';
9
+ type RedirectCompatFamily = 'bluesky-social' | 'standard-site' | 'tangled' | 'margin' | 'grain' | 'pinksky' | 'semble' | 'streamplace' | 'popfeed' | 'sifa' | 'blento' | 'atproto-explorer';
10
10
  type WaypointData = {
11
11
  id: string;
12
12
  name: string;
@@ -110,13 +110,32 @@ declare function parseURI(handle: string, collection?: string, rkey?: string): P
110
110
  /**
111
111
  * Resolve a handle to a DID using the Bluesky API
112
112
  */
113
+ /**
114
+ * Handle resolution that distinguishes a definitive "no such handle" from a
115
+ * transient "resolver unavailable". Callers that need to decide between a real
116
+ * 404 and a retry state use this; `resolveHandle` remains for the common
117
+ * did-or-null case.
118
+ *
119
+ * - `not-found`: the appview returned a 4xx (invalid/unknown handle). Safe to
120
+ * surface as a 404.
121
+ * - `unavailable`: network failure or 5xx. Must NOT be shown as a 404 — it's a
122
+ * real account we couldn't look up right now.
123
+ */
124
+ type HandleResolution = {
125
+ did: string;
126
+ reason?: undefined;
127
+ } | {
128
+ did: null;
129
+ reason: 'not-found' | 'unavailable';
130
+ };
131
+ declare function resolveHandleStatus(handle: string): Promise<HandleResolution>;
113
132
  declare function resolveHandle(handle: string): Promise<string | null>;
114
133
  /**
115
134
  * Get display name from handle or DID
116
135
  */
117
136
  declare function getDisplayName(handle: string, did?: string): string;
118
137
 
119
- type SourceApp = 'bluesky' | 'bluepy' | 'blacksky' | 'reddwarf' | 'witchsky' | 'catsky' | 'deer' | 'anisota' | 'pinksky' | 'leaflet' | 'tangled' | 'margin' | 'pdsls' | 'atptools' | 'semble' | 'streamplace' | 'grain' | 'popfeed' | 'sifa' | 'blento' | 'offprint' | 'pckt' | 'headDetected';
138
+ type SourceApp = 'aturi' | 'aturiExplore' | 'bluesky' | 'bluepy' | 'blacksky' | 'reddwarf' | 'impro' | 'witchsky' | 'deer' | 'mu' | 'anisota' | 'pinksky' | 'leaflet' | 'tangled' | 'margin' | 'pdsls' | 'atptools' | 'semble' | 'streamplace' | 'grain' | 'popfeed' | 'sifa' | 'blento' | 'standardReader' | 'taproot' | 'offprint' | 'pckt' | 'headDetected';
120
139
  type ReverseMatch = {
121
140
  source: SourceApp;
122
141
  parsed: ParsedURI;
@@ -138,6 +157,13 @@ declare function parseAtUri(uri: string): ReverseMatch | null;
138
157
  * worker to decide if a tab is "relevant" before doing more expensive work.
139
158
  */
140
159
  declare const SUPPORTED_HOSTS: string[];
160
+ /**
161
+ * Whether a hostname belongs to a supported waypoint. Prefer this over a raw
162
+ * `SUPPORTED_HOSTS.includes(...)` check: it strips a leading `www.` and also
163
+ * recognizes subdomains of hosts that opt in (e.g. `eclose.anisota.net`), so
164
+ * the extension popup and resolve API treat those tabs as known.
165
+ */
166
+ declare function isSupportedHost(host: string): boolean;
141
167
 
142
168
  type ResolvedWaypoint = {
143
169
  id: string;
@@ -259,4 +285,4 @@ type ResolveViaApiOptions = {
259
285
  */
260
286
  declare function resolveViaApi(input: ResolveApiInput, options?: ResolveViaApiOptions): Promise<ResolveApiResponse>;
261
287
 
262
- export { type BuildWaypointsOptions, CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, type CategorizedWaypointsData, type CompatFamilyMeta, DID_REQUIRED_WAYPOINTS, type ParsedURI, type RedirectCompatFamily, type ResolveApiFailure, type ResolveApiInput, type ResolveApiParsed, type ResolveApiResponse, type ResolveApiSuccess, type ResolveResult, type ResolveUrlOptions, type ResolveViaApiOptions, type ResolvedRecommendation, type ResolvedWaypoint, type ReverseMatch, SUPPORTED_HOSTS, type SourceApp, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, type WaypointActivity, type WaypointCategoryData, type WaypointData, type WaypointType, buildWaypointsForParsed, getCategorizedWaypointsData, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, matchSupportedUrl, parseAtUri, parseURI, resolveAtUri, resolveHandle, resolveUrl, resolveViaApi, waypointActivity };
288
+ export { type BuildWaypointsOptions, CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, type CategorizedWaypointsData, type CompatFamilyMeta, DID_REQUIRED_WAYPOINTS, type HandleResolution, type ParsedURI, type RedirectCompatFamily, type ResolveApiFailure, type ResolveApiInput, type ResolveApiParsed, type ResolveApiResponse, type ResolveApiSuccess, type ResolveResult, type ResolveUrlOptions, type ResolveViaApiOptions, type ResolvedRecommendation, type ResolvedWaypoint, type ReverseMatch, SUPPORTED_HOSTS, type SourceApp, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, type WaypointActivity, type WaypointCategoryData, type WaypointData, type WaypointType, buildWaypointsForParsed, getCategorizedWaypointsData, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, isSupportedHost, matchSupportedUrl, parseAtUri, parseURI, resolveAtUri, resolveHandle, resolveHandleStatus, resolveUrl, resolveViaApi, waypointActivity };
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ type WaypointType = 'post' | 'profile' | 'list' | 'record' | 'unknown';
6
6
  * atproto collections). Display groupings live in `WAYPOINT_CATEGORIES_DATA`
7
7
  * and are independent of this.
8
8
  */
9
- type RedirectCompatFamily = 'bluesky-social' | 'standard-site' | 'tangled' | 'margin' | 'grain' | 'pinksky' | 'semble' | 'streamplace' | 'popfeed' | 'sifa' | 'blento';
9
+ type RedirectCompatFamily = 'bluesky-social' | 'standard-site' | 'tangled' | 'margin' | 'grain' | 'pinksky' | 'semble' | 'streamplace' | 'popfeed' | 'sifa' | 'blento' | 'atproto-explorer';
10
10
  type WaypointData = {
11
11
  id: string;
12
12
  name: string;
@@ -110,13 +110,32 @@ declare function parseURI(handle: string, collection?: string, rkey?: string): P
110
110
  /**
111
111
  * Resolve a handle to a DID using the Bluesky API
112
112
  */
113
+ /**
114
+ * Handle resolution that distinguishes a definitive "no such handle" from a
115
+ * transient "resolver unavailable". Callers that need to decide between a real
116
+ * 404 and a retry state use this; `resolveHandle` remains for the common
117
+ * did-or-null case.
118
+ *
119
+ * - `not-found`: the appview returned a 4xx (invalid/unknown handle). Safe to
120
+ * surface as a 404.
121
+ * - `unavailable`: network failure or 5xx. Must NOT be shown as a 404 — it's a
122
+ * real account we couldn't look up right now.
123
+ */
124
+ type HandleResolution = {
125
+ did: string;
126
+ reason?: undefined;
127
+ } | {
128
+ did: null;
129
+ reason: 'not-found' | 'unavailable';
130
+ };
131
+ declare function resolveHandleStatus(handle: string): Promise<HandleResolution>;
113
132
  declare function resolveHandle(handle: string): Promise<string | null>;
114
133
  /**
115
134
  * Get display name from handle or DID
116
135
  */
117
136
  declare function getDisplayName(handle: string, did?: string): string;
118
137
 
119
- type SourceApp = 'bluesky' | 'bluepy' | 'blacksky' | 'reddwarf' | 'witchsky' | 'catsky' | 'deer' | 'anisota' | 'pinksky' | 'leaflet' | 'tangled' | 'margin' | 'pdsls' | 'atptools' | 'semble' | 'streamplace' | 'grain' | 'popfeed' | 'sifa' | 'blento' | 'offprint' | 'pckt' | 'headDetected';
138
+ type SourceApp = 'aturi' | 'aturiExplore' | 'bluesky' | 'bluepy' | 'blacksky' | 'reddwarf' | 'impro' | 'witchsky' | 'deer' | 'mu' | 'anisota' | 'pinksky' | 'leaflet' | 'tangled' | 'margin' | 'pdsls' | 'atptools' | 'semble' | 'streamplace' | 'grain' | 'popfeed' | 'sifa' | 'blento' | 'standardReader' | 'taproot' | 'offprint' | 'pckt' | 'headDetected';
120
139
  type ReverseMatch = {
121
140
  source: SourceApp;
122
141
  parsed: ParsedURI;
@@ -138,6 +157,13 @@ declare function parseAtUri(uri: string): ReverseMatch | null;
138
157
  * worker to decide if a tab is "relevant" before doing more expensive work.
139
158
  */
140
159
  declare const SUPPORTED_HOSTS: string[];
160
+ /**
161
+ * Whether a hostname belongs to a supported waypoint. Prefer this over a raw
162
+ * `SUPPORTED_HOSTS.includes(...)` check: it strips a leading `www.` and also
163
+ * recognizes subdomains of hosts that opt in (e.g. `eclose.anisota.net`), so
164
+ * the extension popup and resolve API treat those tabs as known.
165
+ */
166
+ declare function isSupportedHost(host: string): boolean;
141
167
 
142
168
  type ResolvedWaypoint = {
143
169
  id: string;
@@ -259,4 +285,4 @@ type ResolveViaApiOptions = {
259
285
  */
260
286
  declare function resolveViaApi(input: ResolveApiInput, options?: ResolveViaApiOptions): Promise<ResolveApiResponse>;
261
287
 
262
- export { type BuildWaypointsOptions, CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, type CategorizedWaypointsData, type CompatFamilyMeta, DID_REQUIRED_WAYPOINTS, type ParsedURI, type RedirectCompatFamily, type ResolveApiFailure, type ResolveApiInput, type ResolveApiParsed, type ResolveApiResponse, type ResolveApiSuccess, type ResolveResult, type ResolveUrlOptions, type ResolveViaApiOptions, type ResolvedRecommendation, type ResolvedWaypoint, type ReverseMatch, SUPPORTED_HOSTS, type SourceApp, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, type WaypointActivity, type WaypointCategoryData, type WaypointData, type WaypointType, buildWaypointsForParsed, getCategorizedWaypointsData, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, matchSupportedUrl, parseAtUri, parseURI, resolveAtUri, resolveHandle, resolveUrl, resolveViaApi, waypointActivity };
288
+ export { type BuildWaypointsOptions, CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, type CategorizedWaypointsData, type CompatFamilyMeta, DID_REQUIRED_WAYPOINTS, type HandleResolution, type ParsedURI, type RedirectCompatFamily, type ResolveApiFailure, type ResolveApiInput, type ResolveApiParsed, type ResolveApiResponse, type ResolveApiSuccess, type ResolveResult, type ResolveUrlOptions, type ResolveViaApiOptions, type ResolvedRecommendation, type ResolvedWaypoint, type ReverseMatch, SUPPORTED_HOSTS, type SourceApp, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, type WaypointActivity, type WaypointCategoryData, type WaypointData, type WaypointType, buildWaypointsForParsed, getCategorizedWaypointsData, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, isSupportedHost, matchSupportedUrl, parseAtUri, parseURI, resolveAtUri, resolveHandle, resolveHandleStatus, resolveUrl, resolveViaApi, waypointActivity };
package/dist/index.js CHANGED
@@ -54,6 +54,11 @@ var COMPAT_FAMILIES = {
54
54
  id: "blento",
55
55
  name: "Blento",
56
56
  description: "Blento profiles."
57
+ },
58
+ "atproto-explorer": {
59
+ id: "atproto-explorer",
60
+ name: "Record explorers",
61
+ description: "Raw AT Protocol record explorers (pdsls, atp.tools, Aturi Explore) that render any record by its AT URI."
57
62
  }
58
63
  };
59
64
  var COMPAT_FAMILY_ORDER = [
@@ -67,7 +72,8 @@ var COMPAT_FAMILY_ORDER = [
67
72
  "streamplace",
68
73
  "popfeed",
69
74
  "sifa",
70
- "blento"
75
+ "blento",
76
+ "atproto-explorer"
71
77
  ];
72
78
  var WAYPOINT_DESTINATIONS_DATA = {
73
79
  aturi: {
@@ -193,7 +199,32 @@ var WAYPOINT_DESTINATIONS_DATA = {
193
199
  return `https://reddwarf.app/profile/${handle}`;
194
200
  },
195
201
  supportedTypes: ["post", "profile", "list", "record"],
196
- category: "blueskyForks",
202
+ category: "blueskyClients",
203
+ redirectCompat: ["bluesky-social"],
204
+ expectedCollections: ["app.bsky."]
205
+ },
206
+ impro: {
207
+ id: "impro",
208
+ name: "Impro",
209
+ description: (collection) => {
210
+ if (collection === "app.bsky.feed.post") return "View post on impro.social";
211
+ if (collection === "app.bsky.graph.list") return "View list on impro.social";
212
+ return "View profile on impro.social";
213
+ },
214
+ getUrl: (handle, collection, rkey) => {
215
+ if (collection && rkey) {
216
+ if (collection === "app.bsky.feed.post") {
217
+ return `https://impro.social/profile/${handle}/post/${rkey}`;
218
+ }
219
+ if (collection === "app.bsky.graph.list") {
220
+ return `https://impro.social/profile/${handle}/lists/${rkey}`;
221
+ }
222
+ return `https://impro.social/profile/${handle}`;
223
+ }
224
+ return `https://impro.social/profile/${handle}`;
225
+ },
226
+ supportedTypes: ["post", "profile", "list", "record"],
227
+ category: "blueskyClients",
197
228
  redirectCompat: ["bluesky-social"],
198
229
  expectedCollections: ["app.bsky."]
199
230
  },
@@ -222,7 +253,7 @@ var WAYPOINT_DESTINATIONS_DATA = {
222
253
  },
223
254
  supportedTypes: ["post", "profile", "list", "record"],
224
255
  category: "devTools",
225
- redirectCompat: []
256
+ redirectCompat: ["atproto-explorer"]
226
257
  },
227
258
  aturiExplore: {
228
259
  id: "aturiExplore",
@@ -243,7 +274,7 @@ var WAYPOINT_DESTINATIONS_DATA = {
243
274
  },
244
275
  supportedTypes: ["post", "profile", "list", "record"],
245
276
  category: "devTools",
246
- redirectCompat: []
277
+ redirectCompat: ["atproto-explorer"]
247
278
  },
248
279
  atptools: {
249
280
  id: "atptools",
@@ -258,7 +289,7 @@ var WAYPOINT_DESTINATIONS_DATA = {
258
289
  },
259
290
  supportedTypes: ["post", "profile", "list", "record"],
260
291
  category: "devTools",
261
- redirectCompat: []
292
+ redirectCompat: ["atproto-explorer"]
262
293
  },
263
294
  bluepy: {
264
295
  id: "bluepy",
@@ -303,44 +334,44 @@ var WAYPOINT_DESTINATIONS_DATA = {
303
334
  redirectCompat: ["bluesky-social"],
304
335
  expectedCollections: ["app.bsky."]
305
336
  },
306
- catsky: {
307
- id: "catsky",
308
- name: "Catsky",
337
+ deer: {
338
+ id: "deer",
339
+ name: "Deer",
309
340
  description: (collection) => {
310
- if (collection === "app.bsky.feed.post") return "View post on catsky.social";
311
- if (collection === "app.bsky.graph.list") return "View list on catsky.social";
312
- return "View profile on catsky.social";
341
+ if (collection === "app.bsky.feed.post") return "View post on deer.social";
342
+ if (collection === "app.bsky.graph.list") return "View list on deer.social";
343
+ return "View profile on deer.social";
313
344
  },
314
345
  getUrl: (handle, collection, rkey) => {
315
346
  if (collection && rkey) {
316
347
  if (collection === "app.bsky.feed.post") {
317
- return `https://catsky.social/profile/${handle}/post/${rkey}`;
348
+ return `https://deer.social/profile/${handle}/post/${rkey}`;
318
349
  }
319
- return `https://catsky.social/profile/${handle}`;
350
+ return `https://deer.social/profile/${handle}`;
320
351
  }
321
- return `https://catsky.social/profile/${handle}`;
352
+ return `https://deer.social/profile/${handle}`;
322
353
  },
323
354
  supportedTypes: ["post", "profile", "list", "record"],
324
355
  category: "blueskyForks",
325
356
  redirectCompat: ["bluesky-social"],
326
357
  expectedCollections: ["app.bsky."]
327
358
  },
328
- deer: {
329
- id: "deer",
330
- name: "Deer",
359
+ mu: {
360
+ id: "mu",
361
+ name: "Mu",
331
362
  description: (collection) => {
332
- if (collection === "app.bsky.feed.post") return "View post on deer.social";
333
- if (collection === "app.bsky.graph.list") return "View list on deer.social";
334
- return "View profile on deer.social";
363
+ if (collection === "app.bsky.feed.post") return "View post on mu.social";
364
+ if (collection === "app.bsky.graph.list") return "View list on mu.social";
365
+ return "View profile on mu.social";
335
366
  },
336
367
  getUrl: (handle, collection, rkey) => {
337
368
  if (collection && rkey) {
338
369
  if (collection === "app.bsky.feed.post") {
339
- return `https://deer.social/profile/${handle}/post/${rkey}`;
370
+ return `https://mu.social/profile/${handle}/post/${rkey}`;
340
371
  }
341
- return `https://deer.social/profile/${handle}`;
372
+ return `https://mu.social/profile/${handle}`;
342
373
  }
343
- return `https://deer.social/profile/${handle}`;
374
+ return `https://mu.social/profile/${handle}`;
344
375
  },
345
376
  supportedTypes: ["post", "profile", "list", "record"],
346
377
  category: "blueskyForks",
@@ -553,14 +584,54 @@ var WAYPOINT_DESTINATIONS_DATA = {
553
584
  category: "publications",
554
585
  redirectCompat: ["standard-site"],
555
586
  expectedCollections: ["pub.leaflet.", "site.standard."]
587
+ },
588
+ standardReader: {
589
+ id: "standardReader",
590
+ name: "Standard Reader",
591
+ description: (collection) => {
592
+ if (collection?.startsWith("site.standard.") || collection?.startsWith("pub.leaflet.")) {
593
+ return "Read document on standard-reader.app";
594
+ }
595
+ return "View documents on standard-reader.app";
596
+ },
597
+ getUrl: (handle, collection, rkey, did) => {
598
+ const identifier = did || handle;
599
+ if (collection && rkey && (collection.startsWith("site.standard.") || collection.startsWith("pub.leaflet."))) {
600
+ return `https://standard-reader.app/a/${identifier}/${rkey}`;
601
+ }
602
+ return `https://standard-reader.app/u/${identifier}`;
603
+ },
604
+ supportedTypes: ["post", "profile", "list", "record"],
605
+ category: "publications",
606
+ redirectCompat: ["standard-site"],
607
+ expectedCollections: ["pub.leaflet.", "site.standard."]
608
+ },
609
+ taproot: {
610
+ id: "taproot",
611
+ name: "Taproot",
612
+ description: (collection) => {
613
+ if (collection) return "Inspect record on atproto.at";
614
+ return "Browse repo on atproto.at";
615
+ },
616
+ getUrl: (handle, collection, rkey, did) => {
617
+ const identifier = did || handle;
618
+ if (collection && rkey) {
619
+ return `https://atproto.at/uri/at://${identifier}/${collection}/${rkey}`;
620
+ }
621
+ return `https://atproto.at/uri/at://${identifier}`;
622
+ },
623
+ supportedTypes: ["post", "profile", "list", "record"],
624
+ category: "devTools",
625
+ redirectCompat: []
556
626
  }
557
627
  };
558
628
  var WAYPOINT_ORDER = [
559
629
  "anisota",
560
630
  "bluesky",
561
631
  "bluepy",
562
- "blacksky",
563
632
  "reddwarf",
633
+ "impro",
634
+ "blacksky",
564
635
  "leaflet",
565
636
  "aturi",
566
637
  "pinksky",
@@ -574,12 +645,14 @@ var WAYPOINT_ORDER = [
574
645
  "anisotaReader",
575
646
  "offprint",
576
647
  "pckt",
648
+ "standardReader",
577
649
  "aturiExplore",
578
650
  "pdsls",
579
651
  "tangled",
580
652
  "atptools",
653
+ "taproot",
581
654
  "witchsky",
582
- "catsky",
655
+ "mu",
583
656
  "deer"
584
657
  ];
585
658
  function getWaypointDataForType(type) {
@@ -674,13 +747,13 @@ var RECOMMENDED_WAYPOINTS = {
674
747
  label: "Recommended for Repos"
675
748
  },
676
749
  "record": {
677
- waypointIds: ["aturiExplore", "pdsls", "atptools"],
750
+ waypointIds: ["aturiExplore", "pdsls", "atptools", "taproot"],
678
751
  label: "Recommended for Records"
679
752
  }
680
753
  };
681
754
  var RECOMMENDED_NAMESPACE_PREFIXES = {
682
755
  "site.standard": {
683
- waypointIds: ["leaflet", "anisotaReader", "offprint", "pckt", "pdsls"],
756
+ waypointIds: ["leaflet", "standardReader", "anisotaReader", "offprint", "pckt", "pdsls"],
684
757
  label: "Recommended for Publications"
685
758
  },
686
759
  "pub.leaflet": {
@@ -748,6 +821,26 @@ function waypointActivity(waypoint, repoCollections) {
748
821
  return "absent";
749
822
  }
750
823
 
824
+ // src/upstreamFetch.ts
825
+ var RETRY_DELAY_MS = 250;
826
+ async function upstreamFetch(url, { timeoutMs = 8e3, retries = 1, ...init } = {}) {
827
+ let lastError;
828
+ for (let attempt = 0; attempt <= retries; attempt++) {
829
+ try {
830
+ return await fetch(url, {
831
+ ...init,
832
+ signal: AbortSignal.timeout(timeoutMs)
833
+ });
834
+ } catch (error) {
835
+ lastError = error;
836
+ if (attempt < retries) {
837
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
838
+ }
839
+ }
840
+ }
841
+ throw lastError;
842
+ }
843
+
751
844
  // src/uriParser.ts
752
845
  function parseURI(handle, collection, rkey) {
753
846
  if (!handle) {
@@ -789,25 +882,29 @@ function parseURI(handle, collection, rkey) {
789
882
  error: "Invalid URI structure"
790
883
  };
791
884
  }
792
- async function resolveHandle(handle) {
885
+ async function resolveHandleStatus(handle) {
793
886
  if (handle.startsWith("did:")) {
794
- return handle;
887
+ return { did: handle };
795
888
  }
796
889
  try {
797
890
  const apiUrl = process.env.NEXT_PUBLIC_BSKY_API_URL || "https://public.api.bsky.app";
798
- const response = await fetch(
891
+ const response = await upstreamFetch(
799
892
  `${apiUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`
800
893
  );
801
894
  if (!response.ok) {
802
- return null;
895
+ return { did: null, reason: response.status >= 500 ? "unavailable" : "not-found" };
803
896
  }
804
897
  const data = await response.json();
805
- return data.did || null;
898
+ if (data.did) return { did: data.did };
899
+ return { did: null, reason: "not-found" };
806
900
  } catch (error) {
807
901
  console.error("Error resolving handle:", error);
808
- return null;
902
+ return { did: null, reason: "unavailable" };
809
903
  }
810
904
  }
905
+ async function resolveHandle(handle) {
906
+ return (await resolveHandleStatus(handle)).did;
907
+ }
811
908
  function getDisplayName(handle, did) {
812
909
  if (handle.startsWith("did:")) {
813
910
  return did ? `@${did.slice(0, 16)}...` : "Unknown";
@@ -820,16 +917,28 @@ var BLUESKY_FAMILY = [
820
917
  { source: "bluesky", hosts: ["bsky.app"] },
821
918
  { source: "blacksky", hosts: ["blacksky.community"] },
822
919
  { source: "reddwarf", hosts: ["reddwarf.app"] },
920
+ { source: "impro", hosts: ["impro.social"] },
823
921
  { source: "witchsky", hosts: ["witchsky.app"] },
824
- { source: "catsky", hosts: ["catsky.social"] },
825
922
  { source: "deer", hosts: ["deer.social"] },
826
- { source: "anisota", hosts: ["anisota.net"] }
923
+ { source: "mu", hosts: ["mu.social"] },
924
+ { source: "anisota", hosts: ["anisota.net"], matchSubdomains: true }
827
925
  ];
926
+ var SUBDOMAIN_HOSTS = BLUESKY_FAMILY.filter((f) => f.matchSubdomains).flatMap(
927
+ (f) => f.hosts
928
+ );
828
929
  function normalizeHost(host) {
829
930
  return host.toLowerCase().replace(/^www\./, "");
830
931
  }
932
+ function isSubdomainOf(host, base) {
933
+ return host.endsWith(`.${base}`);
934
+ }
935
+ function hostMatchesConfig(host, config) {
936
+ if (config.hosts.includes(host)) return true;
937
+ if (!config.matchSubdomains) return false;
938
+ return config.hosts.some((h) => isSubdomainOf(host, h));
939
+ }
831
940
  function matchBlueskyFamily(host, parts) {
832
- const entry = BLUESKY_FAMILY.find((f) => f.hosts.includes(host));
941
+ const entry = BLUESKY_FAMILY.find((f) => hostMatchesConfig(host, f));
833
942
  if (!entry) return null;
834
943
  if (parts[0] !== "profile" || !parts[1]) return null;
835
944
  const handle = parts[1];
@@ -871,6 +980,19 @@ function matchBlueskyFamily(host, parts) {
871
980
  }
872
981
  };
873
982
  }
983
+ if (parts[2] === "document" && parts[3]) {
984
+ return {
985
+ source: entry.source,
986
+ parsed: {
987
+ type: "record",
988
+ uri: `at://${handle}/site.standard.document/${parts[3]}`,
989
+ handle,
990
+ did,
991
+ collection: "site.standard.document",
992
+ rkey: parts[3]
993
+ }
994
+ };
995
+ }
874
996
  return {
875
997
  source: entry.source,
876
998
  parsed: {
@@ -1141,11 +1263,160 @@ function matchBlento(host, parts) {
1141
1263
  parsed: { type: "profile", uri: `at://${handle}`, handle, did }
1142
1264
  };
1143
1265
  }
1266
+ function matchStandardReader(host, parts) {
1267
+ if (host !== "standard-reader.app") return null;
1268
+ if (parts[0] === "a" && parts[1] && parts[2]) {
1269
+ const handle = parts[1];
1270
+ const did = handle.startsWith("did:") ? handle : void 0;
1271
+ const rkey = parts[2];
1272
+ return {
1273
+ source: "standardReader",
1274
+ parsed: {
1275
+ type: "record",
1276
+ uri: `at://${handle}/site.standard.document/${rkey}`,
1277
+ handle,
1278
+ did,
1279
+ collection: "site.standard.document",
1280
+ rkey
1281
+ }
1282
+ };
1283
+ }
1284
+ if (parts[0] === "u" && parts[1]) {
1285
+ const handle = parts[1];
1286
+ const did = handle.startsWith("did:") ? handle : void 0;
1287
+ return {
1288
+ source: "standardReader",
1289
+ parsed: { type: "profile", uri: `at://${handle}`, handle, did }
1290
+ };
1291
+ }
1292
+ return null;
1293
+ }
1294
+ function matchFlatRecordHost(host, parts, target) {
1295
+ if (host !== target.host) return null;
1296
+ const [handle, collection, rkey] = parts;
1297
+ if (!handle || !collection || !rkey) return null;
1298
+ if (!collection.includes(".")) return null;
1299
+ const did = handle.startsWith("did:") ? handle : void 0;
1300
+ return {
1301
+ source: target.source,
1302
+ parsed: {
1303
+ type: inferType(collection),
1304
+ uri: `at://${handle}/${collection}/${rkey}`,
1305
+ handle,
1306
+ did,
1307
+ collection,
1308
+ rkey
1309
+ }
1310
+ };
1311
+ }
1312
+ function matchTaproot(host, pathname) {
1313
+ if (host !== "atproto.at") return null;
1314
+ const m = pathname.replace(/^\/+/, "").match(/^uri\/at:\/{1,2}(.+)$/);
1315
+ if (!m) return null;
1316
+ const segments = m[1].split("/").filter(Boolean);
1317
+ if (segments.length === 0) return null;
1318
+ const handle = segments[0];
1319
+ const collection = segments[1];
1320
+ const rkey = segments[2];
1321
+ const did = handle.startsWith("did:") ? handle : void 0;
1322
+ if (collection && rkey) {
1323
+ return {
1324
+ source: "taproot",
1325
+ parsed: {
1326
+ type: inferType(collection),
1327
+ uri: `at://${handle}/${collection}/${rkey}`,
1328
+ handle,
1329
+ did,
1330
+ collection,
1331
+ rkey
1332
+ }
1333
+ };
1334
+ }
1335
+ return {
1336
+ source: "taproot",
1337
+ parsed: { type: "profile", uri: `at://${handle}`, handle, did }
1338
+ };
1339
+ }
1340
+ function matchAturi(host, parts) {
1341
+ if (host !== "aturi.to") return null;
1342
+ const isIdentifier = (seg) => !!seg && (seg.startsWith("did:") || seg.includes("."));
1343
+ if (parts[0] === "explore" && isIdentifier(parts[1])) {
1344
+ const handle = parts[1];
1345
+ const did = handle.startsWith("did:") ? handle : void 0;
1346
+ const collection = parts[2];
1347
+ const rkey = parts[3];
1348
+ if (collection && rkey) {
1349
+ return {
1350
+ source: "aturiExplore",
1351
+ parsed: {
1352
+ type: inferType(collection),
1353
+ uri: `at://${handle}/${collection}/${rkey}`,
1354
+ handle,
1355
+ did,
1356
+ collection,
1357
+ rkey
1358
+ }
1359
+ };
1360
+ }
1361
+ return {
1362
+ source: "aturiExplore",
1363
+ parsed: { type: "profile", uri: `at://${handle}`, handle, did }
1364
+ };
1365
+ }
1366
+ if (parts[0] === "profile" && isIdentifier(parts[1])) {
1367
+ const handle = parts[1];
1368
+ const did = handle.startsWith("did:") ? handle : void 0;
1369
+ if (parts[2] === "post" && parts[3]) {
1370
+ return {
1371
+ source: "aturi",
1372
+ parsed: {
1373
+ type: "post",
1374
+ uri: `at://${handle}/app.bsky.feed.post/${parts[3]}`,
1375
+ handle,
1376
+ did,
1377
+ collection: "app.bsky.feed.post",
1378
+ rkey: parts[3]
1379
+ }
1380
+ };
1381
+ }
1382
+ if (parts[2] === "lists" && parts[3]) {
1383
+ return {
1384
+ source: "aturi",
1385
+ parsed: {
1386
+ type: "list",
1387
+ uri: `at://${handle}/app.bsky.graph.list/${parts[3]}`,
1388
+ handle,
1389
+ did,
1390
+ collection: "app.bsky.graph.list",
1391
+ rkey: parts[3]
1392
+ }
1393
+ };
1394
+ }
1395
+ if (parts[2] && parts[2].includes(".") && parts[3]) {
1396
+ return {
1397
+ source: "aturi",
1398
+ parsed: {
1399
+ type: inferType(parts[2]),
1400
+ uri: `at://${handle}/${parts[2]}/${parts[3]}`,
1401
+ handle,
1402
+ did,
1403
+ collection: parts[2],
1404
+ rkey: parts[3]
1405
+ }
1406
+ };
1407
+ }
1408
+ return {
1409
+ source: "aturi",
1410
+ parsed: { type: "profile", uri: `at://${handle}`, handle, did }
1411
+ };
1412
+ }
1413
+ return null;
1414
+ }
1144
1415
  function matchSupportedUrl(url) {
1145
1416
  const host = normalizeHost(url.hostname);
1146
1417
  const parts = url.pathname.split("/").filter(Boolean);
1147
1418
  const search = url.searchParams;
1148
- return matchBlueskyFamily(host, parts) || matchPinksky(host, parts, search) || matchLeaflet(host, parts) || matchTangled(host, parts) || matchMargin(host, parts) || matchPdsls(host, url.pathname) || matchAtpTools(host, url.pathname) || matchBluepy(host, url.pathname) || matchSemble(host, parts) || matchStreamplace(host, parts) || matchGrain(host, parts) || matchPopfeed(host, parts) || matchSifa(host, parts) || matchBlento(host, parts);
1419
+ return matchAturi(host, parts) || matchBlueskyFamily(host, parts) || matchPinksky(host, parts, search) || matchLeaflet(host, parts) || matchTangled(host, parts) || matchMargin(host, parts) || matchPdsls(host, url.pathname) || matchAtpTools(host, url.pathname) || matchBluepy(host, url.pathname) || matchSemble(host, parts) || matchStreamplace(host, parts) || matchGrain(host, parts) || matchPopfeed(host, parts) || matchSifa(host, parts) || matchBlento(host, parts) || matchStandardReader(host, parts) || matchFlatRecordHost(host, parts, { host: "offprint.app", source: "offprint" }) || matchFlatRecordHost(host, parts, { host: "pckt.blog", source: "pckt" }) || matchTaproot(host, url.pathname);
1149
1420
  }
1150
1421
  function parseAtUri(uri) {
1151
1422
  if (!uri.startsWith("at://")) return null;
@@ -1175,6 +1446,7 @@ function parseAtUri(uri) {
1175
1446
  };
1176
1447
  }
1177
1448
  var SUPPORTED_HOSTS = [
1449
+ "aturi.to",
1178
1450
  ...BLUESKY_FAMILY.flatMap((e) => e.hosts),
1179
1451
  "pinkleap.app",
1180
1452
  "leaflet.pub",
@@ -1188,8 +1460,17 @@ var SUPPORTED_HOSTS = [
1188
1460
  "grain.social",
1189
1461
  "popfeed.social",
1190
1462
  "sifa.id",
1191
- "blento.app"
1463
+ "blento.app",
1464
+ "standard-reader.app",
1465
+ "offprint.app",
1466
+ "pckt.blog",
1467
+ "atproto.at"
1192
1468
  ];
1469
+ function isSupportedHost(host) {
1470
+ const h = normalizeHost(host);
1471
+ if (SUPPORTED_HOSTS.includes(h)) return true;
1472
+ return SUBDOMAIN_HOSTS.some((base) => isSubdomainOf(h, base));
1473
+ }
1193
1474
 
1194
1475
  // src/resolve.ts
1195
1476
  var DID_REQUIRED_WAYPOINTS = /* @__PURE__ */ new Set([
@@ -1322,6 +1603,6 @@ async function detectAtUriInHead(url, timeoutMs) {
1322
1603
  }
1323
1604
  }
1324
1605
 
1325
- export { CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, DID_REQUIRED_WAYPOINTS, SUPPORTED_HOSTS, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, buildWaypointsForParsed, getCategorizedWaypointsData, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, matchSupportedUrl, parseAtUri, parseURI, resolveAtUri, resolveHandle, resolveUrl, resolveViaApi, waypointActivity };
1606
+ export { CATEGORY_ORDER, COMPAT_FAMILIES, COMPAT_FAMILY_ORDER, DID_REQUIRED_WAYPOINTS, SUPPORTED_HOSTS, WAYPOINT_CATEGORIES_DATA, WAYPOINT_DESTINATIONS_DATA, WAYPOINT_ORDER, buildWaypointsForParsed, getCategorizedWaypointsData, getDisplayName, getFeaturedWaypointData, getRecommendedWaypointsData, getWaypointCountData, getWaypointDataForType, isSupportedHost, matchSupportedUrl, parseAtUri, parseURI, resolveAtUri, resolveHandle, resolveHandleStatus, resolveUrl, resolveViaApi, waypointActivity };
1326
1607
  //# sourceMappingURL=index.js.map
1327
1608
  //# sourceMappingURL=index.js.map