Experiment to rebuild Diffuse using web applets.
0

Configure Feed

Select the types of activity you want to include in your feed.

1import { S3Client } from "@bradenmacdonald/s3-lite-client"; 2import * as IDB from "idb-keyval"; 3import * as URI from "uri-js"; 4import QS from "query-string"; 5 6import type { Track } from "@applets/core/types.d.ts"; 7import { ENCODINGS, IDB_BUCKETS, SCHEME } from "./constants"; 8import type { Bucket } from "./types"; 9 10//////////////////////////////////////////// 11// 🛠️ 12//////////////////////////////////////////// 13export function bucketsFromTracks(tracks: Track[]) { 14 return tracks.reduce((acc: Record<string, Bucket>, track: Track) => { 15 const parsed = parseURI(track.uri); 16 if (!parsed) return acc; 17 18 const id = bucketId(parsed.bucket); 19 if (acc[id]) return acc; 20 21 return { ...acc, [id]: parsed.bucket }; 22 }, {}); 23} 24 25export function bucketId(bucket: Bucket) { 26 return `${bucket.accessKey}:${bucket.secretKey}@${bucket.host}`; 27} 28 29export function buildURI(bucket: Bucket, path: string) { 30 return URI.serialize({ 31 scheme: SCHEME, 32 userinfo: `${bucket.accessKey}:${bucket.secretKey}`, 33 host: bucket.host.replace(/^https?:\/\//, ""), 34 path: path, 35 query: QS.stringify({ 36 bucketName: bucket.bucketName, 37 bucketPath: bucket.path, 38 region: bucket.region, 39 }), 40 }); 41} 42 43export async function consultBucket(bucket: Bucket) { 44 const client = createClient(bucket); 45 return await client.bucketExists(bucket.bucketName); 46} 47 48export function createClient(bucket: Bucket) { 49 return new S3Client({ 50 bucket: bucket.bucketName, 51 endPoint: `http${bucket.host.startsWith("localhost") ? "" : "s"}://${bucket.host}`, 52 region: bucket.region, 53 pathStyle: false, 54 accessKey: bucket.accessKey, 55 secretKey: bucket.secretKey, 56 }); 57} 58 59export function encodeAwsUriComponent(a: string) { 60 return encodeURIComponent(a).replace( 61 /(\+|!|"|#|\$|&|'|\(|\)|\*|\+|,|:|;|=|\?|@)/gim, 62 (match) => (ENCODINGS as any)[match] ?? match, 63 ); 64} 65 66export function groupTracksByBucket(tracks: Track[]) { 67 return tracks.reduce((acc: Record<string, { bucket: Bucket; tracks: Track[] }>, track: Track) => { 68 const parsed = parseURI(track.uri); 69 if (!parsed) return acc; 70 71 const id = bucketId(parsed.bucket); 72 const obj = { bucket: parsed.bucket, tracks: acc[id] ? [...acc[id].tracks, track] : [track] }; 73 74 return { ...acc, [id]: obj }; 75 }, {}); 76} 77 78export async function loadBuckets(): Promise<Record<string, Bucket>> { 79 const i = await IDB.get(IDB_BUCKETS); 80 return i ? i : {}; 81} 82 83export function parseURI(uriString: string): { bucket: Bucket; path: string } | undefined { 84 const uri = URI.parse(uriString); 85 if (uri.scheme !== SCHEME) return undefined; 86 if (!uri.host) return undefined; 87 88 const [accessKey, secretKey] = uri.userinfo?.split(":") ?? []; 89 if (!accessKey || !secretKey) return undefined; 90 91 const qs = QS.parse(uri.query || ""); 92 93 const bucket = { 94 accessKey, 95 bucketName: typeof qs.bucketName === "string" ? qs.bucketName : "", 96 host: uri.host, 97 path: qs.bucketPath === "string" ? qs.bucketPath : "/", 98 region: typeof qs.region === "string" ? qs.region : "", 99 secretKey, 100 }; 101 102 const path = (bucket.path.replace(/\/$/, "") + URI.unescapeComponent(uri.path || "")).replace( 103 /^\//, 104 "", 105 ); 106 107 return { bucket, path }; 108} 109 110export async function saveBuckets(items: Record<string, Bucket>) { 111 await IDB.set(IDB_BUCKETS, items); 112}