[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!

server: return the blob stream with proper mime types

TODO:
- security headers for proper origin sandboxing
(users could store sensitive info + general namespace collisions bad)
- backfill and cdn to take load off of the main appview
this will populate a database which the appview does reads from
and will download any referenced routes (to a certain size per user)
and gc old routes

+67 -18
+67 -18
src/user.ts
··· 141 141 142 142 if (!targetRkey) throw "invalid url"; 143 143 144 - const { ok, data } = await client.get("com.atproto.repo.getRecord", { 145 - params: { 146 - collection: "dev.atcities.route", 147 - repo: did, 148 - rkey: targetRkey, 149 - }, 150 - }); 144 + const { ok: record_ok, data: record_data } = await client.get( 145 + "com.atproto.repo.getRecord", 146 + { 147 + params: { 148 + collection: "dev.atcities.route", 149 + repo: did, 150 + rkey: targetRkey, 151 + }, 152 + } 153 + ); 151 154 152 - if (!ok) { 153 - switch (data.error) { 154 - case "InvalidRequest": 155 - case "ExpiredToken": 156 - case "InvalidToken": { 157 - // tokens arent used and the request should be structured fine 158 - // this is an unexpected error so its fine to 500 exit 159 - throw "Internal Error"; 160 - } 155 + if (!record_ok) { 156 + switch (record_data.error) { 161 157 case "RecordNotFound": { 162 158 // 404 error so try load 404 page 163 159 if (route !== "404") { ··· 173 169 statusText: "Not Found", 174 170 }); 175 171 } 172 + // unless its a 404, internal error 176 173 default: 177 - throw "Unhandled exception"; 174 + throw "Internal Error"; 178 175 } 179 176 } 180 177 181 - if (!is(DevAtcitiesRoute.mainSchema, data.value)) 178 + if (is(DevAtcitiesRoute.mainSchema, record_data.value)) { 179 + const { 180 + ok: blob_ok, 181 + data: blob_data, 182 + headers: blob_headers, 183 + } = await client.get("com.atproto.sync.getBlob", { 184 + params: { 185 + did, 186 + cid: 187 + "ref" in record_data.value.page 188 + ? record_data.value.page.ref.$link 189 + : record_data.value.page.cid, 190 + }, 191 + as: "stream", 192 + }); 193 + 194 + // possible errors include: 195 + // - request issue 196 + // - 404 not found 197 + // - account takedown 198 + // in all cases thats not recoverable 199 + // so throw out and take the happy path 200 + if (!blob_ok) { 201 + if (blob_data.error === "BlobNotFound") { 202 + // 404 error so try load 404 page 203 + if (route !== "404") { 204 + const r404 = await getRoute(did, pds, "404"); 205 + return new Response(r404.body, { 206 + status: 404, 207 + statusText: "Not Found", 208 + headers: r404.headers, 209 + }); 210 + } 211 + return new Response("Could not load page.", { 212 + status: 404, 213 + statusText: "Not Found", 214 + }); 215 + } else throw "Internal Error"; 216 + } 217 + 218 + return new Response(blob_data, { 219 + headers: { 220 + "Content-Type": 221 + (record_data.value.mimeType ?? 222 + record_data.value.page.mimeType !== "application/octet-stream") 223 + ? record_data.value.page.mimeType 224 + : (blob_headers.get("Content-Type") ?? 225 + "application/octet-stream"), 226 + }, 227 + }); 228 + 229 + // isnt valid data so throw exception 230 + } else 182 231 return new Response( 183 232 "Malformed record for at://" + did + "/dev.atcities.route/" + targetRkey 184 233 );