···2233import type { Did } from '@atcute/lexicons';
44import type { Records } from '@atcute/lexicons/ambient';
55+import { fromBase64Url, toBase64Url } from '@atcute/multibase';
66+import { decodeUtf8From, encodeUtf8 } from '@atcute/uint8array';
5768import * as v from '@badrap/valita';
79···6870 return json as LinkResponse<K>;
6971};
70727373+const multiPathCursor = v.tuple([v.string(), v.string().nullable()]);
7474+7575+/**
7676+ * generate multi path cursor
7777+ * @param path current path
7878+ * @param subcursor sub cursor
7979+ * @returns compressed cursor
8080+ */
8181+const generateMultiPathCursor = (path: string, subcursor: string | null): string => {
8282+ const mp: v.Infer<typeof multiPathCursor> = [path, subcursor];
8383+ const json = JSON.stringify(mp);
8484+8585+ return toBase64Url(encodeUtf8(json));
8686+};
8787+7188// due to the way Bluesky has designed its embeds, quotes can be in two
7289// different paths, `.embed.record.uri` and `.embed.record.record.uri`.
7373-// Since Constellation can only support one path at a time, here's a function
9090+//
9191+// since Constellation can only support one path at a time, here's a function
7492// that will make this happen really nicely
7575-const MP_CURSOR_RE = /^mp:(\d+)(?::(.+))?$/;
7676-7793export const getLinksMultiPath = async <K extends keyof Records>({
7894 uri,
7995 collection,
···99115 };
100116101117 if (cursor !== null) {
102102- const match = MP_CURSOR_RE.exec(cursor);
103103- if (match === null) {
104104- return result;
105105- }
118118+ try {
119119+ const raw = decodeUtf8From(fromBase64Url(cursor));
120120+ const json = JSON.parse(raw);
106121107107- index = parseInt(match[1], 10);
108108- curs = match[2] ?? null;
122122+ const [currentPath, subCursor] = multiPathCursor.parse(json);
123123+ const foundIndex = paths.indexOf(currentPath);
124124+125125+ if (foundIndex === -1) {
126126+ return result;
127127+ }
109128110110- if (index >= paths.length) {
129129+ index = foundIndex;
130130+ curs = subCursor;
131131+ } catch (e) {
132132+ // If decompression or parsing fails, treat as invalid cursor
111133 return result;
112134 }
113135 }
···125147126148 // response returned a cursor, so we're breaking early
127149 if (data.cursor !== null) {
128128- result.cursor = `mp:${index}:${data.cursor}`;
150150+ result.cursor = generateMultiPathCursor(paths[index], data.cursor);
129151 break;
130152 }
131153···137159 // move to the next path
138160 index++;
139161 curs = null;
140140- result.cursor = index < paths.length ? `mp:${index}` : null;
162162+ result.cursor = index < paths.length ? generateMultiPathCursor(paths[index], null) : null;
141163 }
142164143165 return result;