A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

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

chore: simplify the settings schema + adapt the passkey transformer to also encrypt setting values

+376 -306
+1 -1
deno.jsonc
··· 148 148 "./components/transformer/output/bytes/automerge/utils.js": "./src/components/transformer/output/bytes/automerge/utils.js", 149 149 "./components/transformer/output/bytes/json/element.js": "./src/components/transformer/output/bytes/json/element.js", 150 150 "./components/transformer/output/refiner/default/element.js": "./src/components/transformer/output/refiner/default/element.js", 151 - "./components/transformer/output/refiner/track-uri-passkey/element.js": "./src/components/transformer/output/refiner/track-uri-passkey/element.js", 151 + "./components/transformer/output/refiner/passkey-encryption/element.js": "./src/components/transformer/output/refiner/passkey-encryption/element.js", 152 152 "./components/transformer/output/string/json/element.js": "./src/components/transformer/output/string/json/element.js", 153 153 154 154 // .d.ts
-1
src/components/orchestrator/scoped-tracks/element.js
··· 174 174 175 175 // Set pool 176 176 this.#proxy.supply({ tracks: availableTracks }); 177 - 178 177 this.#tracksAvailable.set(availableTracks); 179 178 }); 180 179
+350
src/components/transformer/output/refiner/passkey-encryption/element.js
··· 1 + import { computed, signal } from "~/common/signal.js"; 2 + import { OutputTransformer } from "../../base.js"; 3 + 4 + import { 5 + adoptPasskeyPrfResult, 6 + createPasskey, 7 + decryptUri, 8 + deriveCipherKey, 9 + encryptUri, 10 + isEncryptedUri, 11 + loadStoredCipherKey, 12 + removeStoredPasskey, 13 + storeCipherKey, 14 + } from "./passkey.js"; 15 + 16 + /** 17 + * @import { Setting, Track } from "~/definitions/types.d.ts" 18 + * @import { OutputManager } from "~/components/output/types.d.ts" 19 + */ 20 + 21 + /** 22 + * Output transformer that encrypts track URIs and setting values using a 23 + * passkey-derived key. 24 + * 25 + * On read, decrypts `encrypted://` URIs in tracks and `encrypted://`-encoded 26 + * JSON in setting values transparently. On write, re-encrypts before passing 27 + * downstream. 28 + * 29 + * Tracks/settings that cannot be decrypted (no key in memory) are held in 30 + * `lockedTracks`/`lockedSettings` and excluded from the visible collection. 31 + * 32 + * @extends {OutputTransformer} 33 + */ 34 + class PasskeyEncryptionTransformer extends OutputTransformer { 35 + static NAME = "diffuse/transformer/output/refiner/passkey-encryption"; 36 + 37 + #tracks; 38 + 39 + constructor() { 40 + super(); 41 + 42 + const base = this.base(); 43 + 44 + const encryptionKey = this.#encryptionKey; 45 + const lockedSettings = this.#lockedSettings; 46 + const lockedTracks = this.#lockedTracks; 47 + 48 + this.facets = base.facets; 49 + this.playlistItems = base.playlistItems; 50 + this.ready = this.#keyReady.get; 51 + 52 + // Settings 53 + /** @type {OutputManager["settings"]} */ 54 + this.settings = { 55 + ...base.settings, 56 + 57 + collection: computed(() => { 58 + const col = base.settings.collection(); 59 + if (col?.state !== "loaded") return { state: "loading" }; 60 + 61 + const key = encryptionKey.get(); 62 + 63 + /** @type {Setting[]} */ 64 + const unlocked = []; 65 + 66 + /** @type {Setting[]} */ 67 + const locked = []; 68 + 69 + for (const setting of col.data) { 70 + const value = setting.value; 71 + if (typeof value === "string" && isEncryptedUri(value)) { 72 + if (key) { 73 + try { 74 + unlocked.push({ 75 + ...setting, 76 + value: decryptUri(key, value), 77 + }); 78 + } catch { 79 + locked.push(setting); 80 + } 81 + } else { 82 + locked.push(setting); 83 + } 84 + } else { 85 + unlocked.push(setting); 86 + } 87 + } 88 + 89 + lockedSettings.set(locked); 90 + return { state: "loaded", data: unlocked }; 91 + }), 92 + 93 + save: async (/** @type {Setting[]} */ newSettings) => { 94 + const key = encryptionKey.get(); 95 + 96 + if (key) { 97 + newSettings = newSettings.map((setting) => ({ 98 + ...setting, 99 + value: encryptUri(key, setting.value), 100 + })); 101 + 102 + // Re-append still-locked settings so they are not lost 103 + newSettings = newSettings.concat(lockedSettings.value); 104 + } 105 + 106 + await base.settings.save(newSettings); 107 + }, 108 + }; 109 + 110 + // Tracks 111 + this.#tracks = () => { 112 + const col = base.tracks.collection(); 113 + 114 + if (col?.state !== "loaded") { 115 + return { state: "loading", locked: [], unlocked: [] }; 116 + } 117 + 118 + const key = encryptionKey.get(); 119 + 120 + /** @type {Track[]} */ 121 + const unlocked = []; 122 + 123 + /** @type {Track[]} */ 124 + const locked = []; 125 + 126 + for (const track of col.data) { 127 + if (!isEncryptedUri(track.uri)) { 128 + unlocked.push(track); 129 + } else if (key) { 130 + try { 131 + unlocked.push({ ...track, uri: decryptUri(key, track.uri) }); 132 + } catch { 133 + locked.push(track); 134 + } 135 + } else { 136 + locked.push(track); 137 + } 138 + } 139 + 140 + return { state: "loaded", locked, unlocked }; 141 + }; 142 + 143 + /** @type {OutputManager["tracks"]} */ 144 + this.tracks = { 145 + ...base.tracks, 146 + 147 + collection: computed(() => { 148 + const result = this.#tracks(); 149 + if (result.state === "loading") return { state: "loading" }; 150 + lockedTracks.set(result.locked); 151 + return { state: "loaded", data: result.unlocked }; 152 + }), 153 + 154 + save: async (/** @type {Track[]} */ newTracks) => { 155 + const key = encryptionKey.get(); 156 + 157 + if (key) { 158 + newTracks = newTracks.map((track) => ({ 159 + ...track, 160 + uri: encryptUri(key, track.uri), 161 + })); 162 + 163 + // Re-append still-locked tracks so they are not lost 164 + newTracks = newTracks.concat(lockedTracks.value); 165 + } 166 + 167 + await base.tracks.save(newTracks); 168 + }, 169 + }; 170 + } 171 + 172 + // SIGNALS 173 + 174 + #encryptionKey = signal(/** @type {Uint8Array | null} */ (null)); 175 + #keyReady = signal(false); 176 + #lockedSettings = signal(/** @type {Setting[]} */ ([])); 177 + #lockedTracks = signal(/** @type {Track[]} */ ([])); 178 + 179 + passkeyActive = computed(() => this.#encryptionKey.get() !== null); 180 + lockedSettings = this.#lockedSettings.get; 181 + lockedTracks = this.#lockedTracks.get; 182 + 183 + // LIFECYCLE 184 + 185 + /** @override */ 186 + connectedCallback() { 187 + if (this.hasAttribute("group")) { 188 + const channelName = this.namespace?.length 189 + ? `${PasskeyEncryptionTransformer.NAME}/${this.namespace}/${this.group}` 190 + : `${PasskeyEncryptionTransformer.NAME}/${this.group}`; 191 + 192 + const actions = this.broadcast(channelName, { 193 + getLockedSettings: { 194 + strategy: "leaderOnly", 195 + fn: this.#lockedSettings.get, 196 + }, 197 + setLockedSettings: { 198 + strategy: "replicate", 199 + fn: this.#lockedSettings.set, 200 + }, 201 + getLockedTracks: { 202 + strategy: "leaderOnly", 203 + fn: this.#lockedTracks.get, 204 + }, 205 + setLockedTracks: { 206 + strategy: "replicate", 207 + fn: this.#lockedTracks.set, 208 + }, 209 + }); 210 + 211 + if (actions) { 212 + this.#lockedSettings.set = actions.setLockedSettings; 213 + this.#lockedTracks.set = actions.setLockedTracks; 214 + 215 + actions.getLockedSettings().then((locked) => { 216 + this.#lockedSettings.value = locked; 217 + }); 218 + 219 + actions.getLockedTracks().then((locked) => { 220 + this.#lockedTracks.value = locked; 221 + }); 222 + } 223 + } 224 + 225 + super.connectedCallback(); 226 + 227 + loadStoredCipherKey(this.namespace ?? "").then((key) => { 228 + if (key) { 229 + this.#encryptionKey.value = key; 230 + } 231 + 232 + this.#keyReady.value = true; 233 + }); 234 + } 235 + 236 + // PASSKEY 237 + 238 + /** 239 + * Register a new passkey for track URI encryption. 240 + * Throws if the authenticator does not support the PRF extension. 241 + */ 242 + async setupPasskey() { 243 + const namespace = this.namespace ?? ""; 244 + const result = await createPasskey(namespace); 245 + 246 + if (!result.supported) { 247 + throw new Error(result.reason); 248 + } 249 + 250 + const key = await deriveCipherKey(result.prfSecond); 251 + await storeCipherKey(namespace, key); 252 + this.#encryptionKey.value = key; 253 + 254 + let savedSettings = false; 255 + let savedTracks = false; 256 + 257 + const stopSettings = this.effect(() => { 258 + if (savedSettings) { stopSettings(); return; } 259 + const col = this.settings.collection(); 260 + if (col.state === "loading") return; 261 + savedSettings = true; 262 + this.settings.save(col.data); 263 + }); 264 + 265 + const stopTracks = this.effect(() => { 266 + if (savedTracks) { stopTracks(); return; } 267 + const col = this.tracks.collection(); 268 + if (col.state === "loading") return; 269 + savedTracks = true; 270 + this.tracks.save(col.data); 271 + }); 272 + } 273 + 274 + /** 275 + * Adopt an existing passkey from another device via discoverable-credential 276 + * lookup. Stores the credential ID locally and derives the cipher key. 277 + */ 278 + async adoptPasskey() { 279 + const namespace = this.namespace ?? ""; 280 + const result = await adoptPasskeyPrfResult(namespace); 281 + 282 + if (!result.supported) { 283 + throw new Error(result.reason); 284 + } 285 + 286 + const key = await deriveCipherKey(result.prfSecond); 287 + await storeCipherKey(namespace, key); 288 + this.#encryptionKey.value = key; 289 + 290 + let savedSettings = false; 291 + let savedTracks = false; 292 + 293 + const stopSettings = this.effect(() => { 294 + if (savedSettings) { stopSettings(); return; } 295 + const col = this.settings.collection(); 296 + if (col.state !== "loaded") return; 297 + savedSettings = true; 298 + this.settings.save(col.data); 299 + }); 300 + 301 + const stopTracks = this.effect(() => { 302 + if (savedTracks) { stopTracks(); return; } 303 + const col = this.tracks.collection(); 304 + if (col.state !== "loaded") return; 305 + savedTracks = true; 306 + this.tracks.save(col.data); 307 + }); 308 + } 309 + 310 + /** 311 + * Remove the stored passkey credential and clear in-memory key material. 312 + */ 313 + async removePasskey() { 314 + const namespace = this.namespace ?? ""; 315 + await removeStoredPasskey(namespace); 316 + 317 + // Both collections must be captured in the same reactive snapshot before 318 + // clearing the key. If the key were cleared between the two reads, the 319 + // second collection would evaluate with key=null and show encrypted items 320 + // as locked (invisible), causing them to be silently dropped on save. 321 + let removed = false; 322 + 323 + const stop = this.effect(() => { 324 + if (removed) { stop(); return; } 325 + 326 + const settingsCol = this.settings.collection(); 327 + const tracksCol = this.tracks.collection(); 328 + 329 + if (settingsCol.state !== "loaded" || tracksCol.state !== "loaded") return; 330 + 331 + removed = true; 332 + 333 + this.#encryptionKey.value = null; 334 + 335 + this.settings.save(settingsCol.data); 336 + this.tracks.save(tracksCol.data); 337 + }); 338 + } 339 + } 340 + 341 + export default PasskeyEncryptionTransformer; 342 + 343 + //////////////////////////////////////////// 344 + // REGISTER 345 + //////////////////////////////////////////// 346 + 347 + export const CLASS = PasskeyEncryptionTransformer; 348 + export const NAME = "dtor-passkey-encryption"; 349 + 350 + customElements.define(NAME, CLASS);
-263
src/components/transformer/output/refiner/track-uri-passkey/element.js
··· 1 - import { computed, signal } from "~/common/signal.js"; 2 - import { OutputTransformer } from "../../base.js"; 3 - 4 - import { 5 - adoptPasskeyPrfResult, 6 - createPasskey, 7 - decryptUri, 8 - deriveCipherKey, 9 - encryptUri, 10 - isEncryptedUri, 11 - loadStoredCipherKey, 12 - removeStoredPasskey, 13 - storeCipherKey, 14 - } from "./passkey.js"; 15 - 16 - /** 17 - * @import { Track } from "~/definitions/types.d.ts" 18 - * @import { OutputManager } from "~/components/output/types.d.ts" 19 - */ 20 - 21 - /** 22 - * Output transformer that encrypts track URIs using a passkey-derived key. 23 - * 24 - * Sits in front of any output element. On read (`tracks.collection`), 25 - * decrypts `encrypted://` URIs transparently. On write (`tracks.save`), 26 - * re-encrypts all URIs before passing them downstream. 27 - * 28 - * Tracks whose URIs cannot be decrypted (no key in memory) are held 29 - * in `lockedTracks` and excluded from the visible collection. 30 - * 31 - * @extends {OutputTransformer} 32 - */ 33 - class TrackUriPasskeyTransformer extends OutputTransformer { 34 - static NAME = "diffuse/transformer/output/refiner/track-uri-passkey"; 35 - 36 - #tracks; 37 - 38 - constructor() { 39 - super(); 40 - 41 - const base = this.base(); 42 - 43 - const encryptionKey = this.#encryptionKey; 44 - const lockedTracks = this.#lockedTracks; 45 - 46 - this.facets = base.facets; 47 - this.playlistItems = base.playlistItems; 48 - this.settings = base.settings; 49 - this.ready = this.#keyReady.get; 50 - 51 - // Tracks 52 - this.#tracks = () => { 53 - const col = base.tracks.collection(); 54 - 55 - if (col?.state !== "loaded") { 56 - return { state: "loading", locked: [], unlocked: [] }; 57 - } 58 - 59 - const key = encryptionKey.get(); 60 - 61 - /** @type {Track[]} */ 62 - const unlocked = []; 63 - 64 - /** @type {Track[]} */ 65 - const locked = []; 66 - 67 - for (const track of col.data) { 68 - if (!isEncryptedUri(track.uri)) { 69 - unlocked.push(track); 70 - } else if (key) { 71 - try { 72 - unlocked.push({ ...track, uri: decryptUri(key, track.uri) }); 73 - } catch { 74 - locked.push(track); 75 - } 76 - } else { 77 - locked.push(track); 78 - } 79 - } 80 - 81 - return { state: "loaded", locked, unlocked }; 82 - }; 83 - 84 - /** @type {OutputManager["tracks"]} */ 85 - this.tracks = { 86 - ...base.tracks, 87 - 88 - collection: computed(() => { 89 - const result = this.#tracks(); 90 - if (result.state === "loading") return { state: "loading" }; 91 - lockedTracks.set(result.locked); 92 - return { state: "loaded", data: result.unlocked }; 93 - }), 94 - 95 - save: async (/** @type {Track[]} */ newTracks) => { 96 - const key = encryptionKey.get(); 97 - 98 - if (key) { 99 - newTracks = newTracks.map((track) => ({ 100 - ...track, 101 - uri: encryptUri(key, track.uri), 102 - })); 103 - 104 - // Re-append still-locked tracks so they are not lost 105 - newTracks = newTracks.concat(lockedTracks.value); 106 - } 107 - 108 - await base.tracks.save(newTracks); 109 - }, 110 - }; 111 - } 112 - 113 - // SIGNALS 114 - 115 - #encryptionKey = signal(/** @type {Uint8Array | null} */ (null)); 116 - #keyReady = signal(false); 117 - #lockedTracks = signal(/** @type {Track[]} */ ([])); 118 - 119 - passkeyActive = computed(() => this.#encryptionKey.get() !== null); 120 - lockedTracks = this.#lockedTracks.get; 121 - 122 - // LIFECYCLE 123 - 124 - /** @override */ 125 - connectedCallback() { 126 - if (this.hasAttribute("group")) { 127 - const channelName = this.namespace?.length 128 - ? `${TrackUriPasskeyTransformer.NAME}/${this.namespace}/${this.group}` 129 - : `${TrackUriPasskeyTransformer.NAME}/${this.group}`; 130 - 131 - const actions = this.broadcast(channelName, { 132 - getLockedTracks: { 133 - strategy: "leaderOnly", 134 - fn: this.#lockedTracks.get, 135 - }, 136 - setLockedTracks: { 137 - strategy: "replicate", 138 - fn: this.#lockedTracks.set, 139 - }, 140 - }); 141 - 142 - if (actions) { 143 - this.#lockedTracks.set = actions.setLockedTracks; 144 - 145 - actions.getLockedTracks().then((locked) => { 146 - this.#lockedTracks.value = locked; 147 - }); 148 - } 149 - } 150 - 151 - super.connectedCallback(); 152 - 153 - loadStoredCipherKey(this.namespace ?? "").then((key) => { 154 - if (key) { 155 - this.#encryptionKey.value = key; 156 - } 157 - 158 - this.#keyReady.value = true; 159 - }); 160 - } 161 - 162 - // PASSKEY 163 - 164 - /** 165 - * Register a new passkey for track URI encryption. 166 - * Throws if the authenticator does not support the PRF extension. 167 - */ 168 - async setupPasskey() { 169 - const namespace = this.namespace ?? ""; 170 - const result = await createPasskey(namespace); 171 - 172 - if (!result.supported) { 173 - throw new Error(result.reason); 174 - } 175 - 176 - const key = await deriveCipherKey(result.prfSecond); 177 - await storeCipherKey(namespace, key); 178 - this.#encryptionKey.value = key; 179 - 180 - let saved = false; 181 - 182 - const stop = this.effect(() => { 183 - if (saved) { 184 - stop(); 185 - return; 186 - } 187 - 188 - const col = this.tracks.collection(); 189 - if (col.state === "loading") return; 190 - 191 - saved = true; 192 - this.tracks.save(col.data); 193 - }); 194 - } 195 - 196 - /** 197 - * Adopt an existing passkey from another device via discoverable-credential 198 - * lookup. Stores the credential ID locally and derives the cipher key. 199 - */ 200 - async adoptPasskey() { 201 - const namespace = this.namespace ?? ""; 202 - const result = await adoptPasskeyPrfResult(namespace); 203 - 204 - if (!result.supported) { 205 - throw new Error(result.reason); 206 - } 207 - 208 - const key = await deriveCipherKey(result.prfSecond); 209 - await storeCipherKey(namespace, key); 210 - this.#encryptionKey.value = key; 211 - 212 - let saved = false; 213 - 214 - const stop = this.effect(() => { 215 - if (saved) { 216 - stop(); 217 - return; 218 - } 219 - 220 - const col = this.tracks.collection(); 221 - if (col.state !== "loaded") return; 222 - 223 - saved = true; 224 - this.tracks.save(col.data); 225 - }); 226 - } 227 - 228 - /** 229 - * Remove the stored passkey credential and clear in-memory key material. 230 - */ 231 - async removePasskey() { 232 - const namespace = this.namespace ?? ""; 233 - await removeStoredPasskey(namespace); 234 - 235 - let removed = false; 236 - 237 - const stop = this.effect(() => { 238 - if (removed) { 239 - stop(); 240 - return; 241 - } 242 - 243 - const col = this.tracks.collection(); 244 - if (col.state !== "loaded") return; 245 - 246 - removed = true; 247 - 248 - this.#encryptionKey.value = null; 249 - this.tracks.save(col.data); 250 - }); 251 - } 252 - } 253 - 254 - export default TrackUriPasskeyTransformer; 255 - 256 - //////////////////////////////////////////// 257 - // REGISTER 258 - //////////////////////////////////////////// 259 - 260 - export const CLASS = TrackUriPasskeyTransformer; 261 - export const NAME = "dtor-track-uri-passkey"; 262 - 263 - customElements.define(NAME, CLASS);
+1 -1
src/components/transformer/output/refiner/track-uri-passkey/passkey.js src/components/transformer/output/refiner/passkey-encryption/passkey.js
··· 9 9 // CONSTANTS 10 10 //////////////////////////////////////////// 11 11 12 - const IDB_PREFIX = "diffuse/transformer/output/refiner/track-uri-passkey"; 12 + const IDB_PREFIX = "diffuse/transformer/output/refiner/passkey-encryption"; 13 13 14 14 /** 15 15 * @param {string} namespace
+3 -22
src/definitions/output/setting.json
··· 6 6 "type": "record", 7 7 "record": { 8 8 "type": "object", 9 - "required": ["id", "setting"], 9 + "required": ["id", "key", "value"], 10 10 "properties": { 11 11 "id": { "type": "string" }, 12 12 "createdAt": { "type": "string", "format": "datetime" }, 13 13 "updatedAt": { "type": "string", "format": "datetime" }, 14 - "setting": { 15 - "type": "union", 16 - "description": "The key and value of a setting", 17 - "refs": ["#sh_diffuse_input_disabled_uris", "#untyped_setting"] 18 - } 14 + "key": { "type": "string" }, 15 + "value": { "type": "string" } 19 16 } 20 - } 21 - }, 22 - "sh_diffuse_input_disabled_uris": { 23 - "type": "object", 24 - "required": ["key", "value"], 25 - "properties": { 26 - "key": { "type": "string", "const": "sh.diffuse.input.disabled.uris" }, 27 - "value": { "type": "array", "items": { "type": "string" } } 28 - } 29 - }, 30 - "untyped_setting": { 31 - "type": "object", 32 - "required": ["key", "value"], 33 - "properties": { 34 - "key": { "type": "string" }, 35 - "value": { "type": "unknown" } 36 17 } 37 18 } 38 19 }
+1 -1
src/elements.vto
··· 180 180 url: "components/transformer/output/refiner/default/element.js" 181 181 - title: "Output / Refiner / Track URI Passkey" 182 182 desc: "Encrypts track URIs using a passkey-derived PRF key. On read, decrypts `encrypted://` URIs transparently; on write, re-encrypts all URIs before passing downstream. Tracks that cannot be decrypted are held separately and excluded from the visible collection." 183 - url: "components/transformer/output/refiner/track-uri-passkey/element.js" 183 + url: "components/transformer/output/refiner/passkey-encryption/element.js" 184 184 - title: "Output / String / JSON" 185 185 desc: "Raw data schema output ⇄ JSON UTF8 string." 186 186 url: "components/transformer/output/string/json/element.js"
+3 -3
src/facets/connect/atproto/index.inline.js
··· 1 1 import { html, nothing, render as litRender } from "lit-html"; 2 2 3 3 import { NAME as ATPROTO_NAME } from "~/components/output/raw/atproto/element.js"; 4 - import { NAME as PASSKEY_NAME } from "~/components/transformer/output/refiner/track-uri-passkey/element.js"; 4 + import { NAME as PASSKEY_NAME } from "~/components/transformer/output/refiner/passkey-encryption/element.js"; 5 5 import { effect, signal } from "~/common/signal.js"; 6 6 import foundation from "~/common/foundation.js"; 7 7 ··· 11 11 12 12 /** 13 13 * @import { ATProtoOutputElement } from "~/components/output/raw/atproto/types.d.ts" 14 - * @import TrackUriPasskeyTransformer from "~/components/transformer/output/refiner/track-uri-passkey/element.js" 14 + * @import PasskeyEncryptionTransformer from "~/components/transformer/output/refiner/passkey-encryption/element.js" 15 15 */ 16 16 17 17 //////////////////////////////////////////// ··· 52 52 outputOrchestrator.root().querySelector(ATPROTO_NAME) 53 53 ); 54 54 55 - const atprotoPasskeyEl = /** @type {TrackUriPasskeyTransformer | null} */ ( 55 + const atprotoPasskeyEl = /** @type {PasskeyEncryptionTransformer | null} */ ( 56 56 outputOrchestrator.root().querySelector( 57 57 `${PASSKEY_NAME}[namespace="atproto"]`, 58 58 )
+2 -2
src/facets/data/output-bundle/index.inline.js
··· 3 3 4 4 import { NAME as ATPROTO_OUTPUT_NAME } from "~/components/output/raw/atproto/element.js"; 5 5 import { NAME as ATPROTO_SYNC_NAME } from "~/components/transformer/output/raw/atproto-sync/element.js"; 6 - import { NAME as ATPROTO_PASSKEY_NAME } from "~/components/transformer/output/refiner/track-uri-passkey/element.js"; 6 + import { NAME as ATPROTO_PASSKEY_NAME } from "~/components/transformer/output/refiner/passkey-encryption/element.js"; 7 7 8 8 import { NAME as S3_OUTPUT_NAME } from "~/components/output/bytes/s3/element.js"; 9 9 import { NAME as S3_SYNC_NAME } from "~/components/transformer/output/bytes/dasl-sync/element.js"; ··· 95 95 "~/components/transformer/output/raw/atproto-sync/element.js" 96 96 ); 97 97 import( 98 - "~/components/transformer/output/refiner/track-uri-passkey/element.js" 98 + "~/components/transformer/output/refiner/passkey-encryption/element.js" 99 99 ); 100 100 } 101 101
+2 -1
tests/components/transformer/output/bytes/json/test.ts
··· 123 123 { 124 124 $type: "sh.diffuse.output.setting", 125 125 id: "s1", 126 - setting: { key: "sh.diffuse.input.disabled.uris", value: [] }, 126 + key: "sh.diffuse.input.disabled.uris", 127 + value: "", 127 128 }, 128 129 ]); 129 130
+2 -1
tests/components/transformer/output/refiner/default/test.ts
··· 133 133 { 134 134 $type: "sh.diffuse.output.setting", 135 135 id: "s1", 136 - setting: { key: "sh.diffuse.input.disabled.uris", value: [] }, 136 + key: "sh.diffuse.input.disabled.uris", 137 + value: "", 137 138 }, 138 139 ]); 139 140
+2 -1
tests/components/transformer/output/refiner/initial-contents/test.ts
··· 147 147 { 148 148 $type: "sh.diffuse.output.setting", 149 149 id: "s1", 150 - setting: { key: "sh.diffuse.input.disabled.uris", value: [] }, 150 + key: "sh.diffuse.input.disabled.uris", 151 + value: "", 151 152 }, 152 153 ]); 153 154
+9 -9
tests/components/transformer/output/refiner/track-uri-passkey/test.ts tests/components/transformer/output/refiner/passkey-encryption/test.ts
··· 3 3 4 4 import { testWeb } from "@tests/common/index.ts"; 5 5 6 - describe("components/transformer/output/refiner/track-uri-passkey", () => { 6 + describe("components/transformer/output/refiner/passkey-encryption", () => { 7 7 // Crypto utilities from passkey.js 8 8 9 9 it("isEncryptedUri returns true for encrypted:// URIs", async () => { 10 10 const result = await testWeb(async () => { 11 11 const mod = await import( 12 - "~/components/transformer/output/refiner/track-uri-passkey/passkey.js" 12 + "~/components/transformer/output/refiner/passkey-encryption/passkey.js" 13 13 ); 14 14 return mod.isEncryptedUri("encrypted://abc123def456"); 15 15 }); ··· 20 20 it("isEncryptedUri returns false for plain URIs", async () => { 21 21 const results = await testWeb(async () => { 22 22 const mod = await import( 23 - "~/components/transformer/output/refiner/track-uri-passkey/passkey.js" 23 + "~/components/transformer/output/refiner/passkey-encryption/passkey.js" 24 24 ); 25 25 return [ 26 26 mod.isEncryptedUri("https://example.com/track.mp3"), ··· 36 36 it("encryptUri and decryptUri round-trip preserves the original URI", async () => { 37 37 const result = await testWeb(async () => { 38 38 const mod = await import( 39 - "~/components/transformer/output/refiner/track-uri-passkey/passkey.js" 39 + "~/components/transformer/output/refiner/passkey-encryption/passkey.js" 40 40 ); 41 41 42 42 const key = crypto.getRandomValues(new Uint8Array(32)); ··· 55 55 it("encryptUri produces different ciphertext each time (managed nonce)", async () => { 56 56 const result = await testWeb(async () => { 57 57 const mod = await import( 58 - "~/components/transformer/output/refiner/track-uri-passkey/passkey.js" 58 + "~/components/transformer/output/refiner/passkey-encryption/passkey.js" 59 59 ); 60 60 61 61 const key = crypto.getRandomValues(new Uint8Array(32)); ··· 72 72 it("decryptUri with wrong key throws", async () => { 73 73 const result = await testWeb(async () => { 74 74 const mod = await import( 75 - "~/components/transformer/output/refiner/track-uri-passkey/passkey.js" 75 + "~/components/transformer/output/refiner/passkey-encryption/passkey.js" 76 76 ); 77 77 78 78 const key1 = crypto.getRandomValues(new Uint8Array(32)); ··· 95 95 it("passkeyActive returns false when no key is loaded", async () => { 96 96 const result = await testWeb(async () => { 97 97 const mod = await import( 98 - "~/components/transformer/output/refiner/track-uri-passkey/element.js" 98 + "~/components/transformer/output/refiner/passkey-encryption/element.js" 99 99 ); 100 100 const t = new mod.CLASS(); 101 101 document.body.append(t); ··· 108 108 it("lockedTracks returns empty array initially", async () => { 109 109 const result = await testWeb(async () => { 110 110 const mod = await import( 111 - "~/components/transformer/output/refiner/track-uri-passkey/element.js" 111 + "~/components/transformer/output/refiner/passkey-encryption/element.js" 112 112 ); 113 113 const t = new mod.CLASS(); 114 114 document.body.append(t); ··· 124 124 "~/components/output/polymorphic/indexed-db/element.js" 125 125 ); 126 126 const mod = await import( 127 - "~/components/transformer/output/refiner/track-uri-passkey/element.js" 127 + "~/components/transformer/output/refiner/passkey-encryption/element.js" 128 128 ); 129 129 130 130 // connectedCallback calls super.connectedCallback() which requires