this repo has no description

feat: Implement OAuth flow and untrack dist

+1 -1
.gitignore
··· 3 3 Cargo.lock 4 4 5 5 # Trunk build output 6 - dist/ 6 + rust_demo_app/app_demo/dist/
+137
rust_demo_app/Cargo.lock
··· 94 94 "log", 95 95 "serde", 96 96 "serde-wasm-bindgen 0.6.5", 97 + "serde_html_form", 97 98 "serde_json", 99 + "serde_qs", 98 100 "types_demo", 99 101 "wasm-bindgen", 100 102 "wasm-bindgen-futures", 101 103 "wasm-logger", 102 104 "web-sys", 103 105 "yew", 106 + "yew-router", 107 + "yewdux", 104 108 ] 105 109 106 110 [[package]] ··· 516 520 ] 517 521 518 522 [[package]] 523 + name = "darling" 524 + version = "0.20.11" 525 + source = "registry+https://github.com/rust-lang/crates.io-index" 526 + checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 527 + dependencies = [ 528 + "darling_core", 529 + "darling_macro", 530 + ] 531 + 532 + [[package]] 533 + name = "darling_core" 534 + version = "0.20.11" 535 + source = "registry+https://github.com/rust-lang/crates.io-index" 536 + checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 537 + dependencies = [ 538 + "fnv", 539 + "ident_case", 540 + "proc-macro2", 541 + "quote", 542 + "strsim", 543 + "syn 2.0.101", 544 + ] 545 + 546 + [[package]] 547 + name = "darling_macro" 548 + version = "0.20.11" 549 + source = "registry+https://github.com/rust-lang/crates.io-index" 550 + checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 551 + dependencies = [ 552 + "darling_core", 553 + "quote", 554 + "syn 2.0.101", 555 + ] 556 + 557 + [[package]] 519 558 name = "dashmap" 520 559 version = "6.1.0" 521 560 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1197 1236 source = "registry+https://github.com/rust-lang/crates.io-index" 1198 1237 checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" 1199 1238 dependencies = [ 1239 + "futures-channel", 1200 1240 "gloo-events 0.2.0", 1201 1241 "js-sys", 1202 1242 "wasm-bindgen", ··· 1365 1405 source = "registry+https://github.com/rust-lang/crates.io-index" 1366 1406 checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 1367 1407 dependencies = [ 1408 + "futures-channel", 1409 + "futures-core", 1368 1410 "js-sys", 1369 1411 "wasm-bindgen", 1370 1412 ] ··· 1815 1857 ] 1816 1858 1817 1859 [[package]] 1860 + name = "ident_case" 1861 + version = "1.0.1" 1862 + source = "registry+https://github.com/rust-lang/crates.io-index" 1863 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1864 + 1865 + [[package]] 1818 1866 name = "idna" 1819 1867 version = "1.0.3" 1820 1868 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2745 2793 ] 2746 2794 2747 2795 [[package]] 2796 + name = "route-recognizer" 2797 + version = "0.3.1" 2798 + source = "registry+https://github.com/rust-lang/crates.io-index" 2799 + checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" 2800 + 2801 + [[package]] 2748 2802 name = "rustc-demangle" 2749 2803 version = "0.1.24" 2750 2804 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3021 3075 ] 3022 3076 3023 3077 [[package]] 3078 + name = "serde_qs" 3079 + version = "0.12.0" 3080 + source = "registry+https://github.com/rust-lang/crates.io-index" 3081 + checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" 3082 + dependencies = [ 3083 + "percent-encoding", 3084 + "serde", 3085 + "thiserror 1.0.69", 3086 + ] 3087 + 3088 + [[package]] 3024 3089 name = "serde_spanned" 3025 3090 version = "0.6.8" 3026 3091 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3205 3270 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 3206 3271 3207 3272 [[package]] 3273 + name = "strsim" 3274 + version = "0.11.1" 3275 + source = "registry+https://github.com/rust-lang/crates.io-index" 3276 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3277 + 3278 + [[package]] 3208 3279 name = "subtle" 3209 3280 version = "2.6.1" 3210 3281 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3660 3731 "idna", 3661 3732 "percent-encoding", 3662 3733 ] 3734 + 3735 + [[package]] 3736 + name = "urlencoding" 3737 + version = "2.1.3" 3738 + source = "registry+https://github.com/rust-lang/crates.io-index" 3739 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 3663 3740 3664 3741 [[package]] 3665 3742 name = "utf16_iter" ··· 4238 4315 "boolinator", 4239 4316 "once_cell", 4240 4317 "prettyplease", 4318 + "proc-macro-error", 4319 + "proc-macro2", 4320 + "quote", 4321 + "syn 2.0.101", 4322 + ] 4323 + 4324 + [[package]] 4325 + name = "yew-router" 4326 + version = "0.18.0" 4327 + source = "registry+https://github.com/rust-lang/crates.io-index" 4328 + checksum = "4ca1d5052c96e6762b4d6209a8aded597758d442e6c479995faf0c7b5538e0c6" 4329 + dependencies = [ 4330 + "gloo 0.10.0", 4331 + "js-sys", 4332 + "route-recognizer", 4333 + "serde", 4334 + "serde_urlencoded", 4335 + "tracing", 4336 + "urlencoding", 4337 + "wasm-bindgen", 4338 + "web-sys", 4339 + "yew", 4340 + "yew-router-macro", 4341 + ] 4342 + 4343 + [[package]] 4344 + name = "yew-router-macro" 4345 + version = "0.18.0" 4346 + source = "registry+https://github.com/rust-lang/crates.io-index" 4347 + checksum = "42bfd190a07ca8cfde7cd4c52b3ac463803dc07323db8c34daa697e86365978c" 4348 + dependencies = [ 4349 + "proc-macro2", 4350 + "quote", 4351 + "syn 2.0.101", 4352 + ] 4353 + 4354 + [[package]] 4355 + name = "yewdux" 4356 + version = "0.11.0" 4357 + source = "registry+https://github.com/rust-lang/crates.io-index" 4358 + checksum = "8030a7de50678c07c038dcb96a42f1e8a7c4cc5610451fbee0c676aa7df42967" 4359 + dependencies = [ 4360 + "log", 4361 + "serde", 4362 + "serde_json", 4363 + "slab", 4364 + "thiserror 1.0.69", 4365 + "wasm-bindgen", 4366 + "web-sys", 4367 + "yew", 4368 + "yewdux-macros", 4369 + ] 4370 + 4371 + [[package]] 4372 + name = "yewdux-macros" 4373 + version = "0.11.0" 4374 + source = "registry+https://github.com/rust-lang/crates.io-index" 4375 + checksum = "e7ac6ccd84a49bbce44610d44eb6686a1266337d0cd3aeadb5564ab76a2819f0" 4376 + dependencies = [ 4377 + "darling", 4241 4378 "proc-macro-error", 4242 4379 "proc-macro2", 4243 4380 "quote",
+64
rust_demo_app/DESIGN.md
··· 1 + # Rust ATProto Counter Demo - Design Overview 2 + 3 + This document outlines the design and goals of the Rust ATProto Counter Demo application. 4 + 5 + ## 1. Project Goal 6 + 7 + The primary goal of this project is to build a simple web application using Rust (compiled to WebAssembly with Yew) that demonstrates integration with the AT Protocol. The core functionality will be a distributed counter. 8 + 9 + Users will be able to: 10 + 1. Authenticate with their AT Protocol (Bluesky) account via OAuth. 11 + 2. Increment a counter. 12 + 3. When a user increments the counter, the application will publish a unique, singleton record to their personal data repository (PDS). This record signifies their participation in the count. 13 + 4. (Future) The frontend might eventually listen to a firehose stream for these specific records from all users to display a global count. 14 + 15 + This project serves as a learning exercise and a demonstration of using the `atrium-rs` SDK for AT Protocol interactions in a Rust Wasm context. 16 + 17 + ## 2. Project Structure 18 + 19 + The `rust_demo_app/` directory contains the core application code, structured as a Cargo workspace: 20 + 21 + * **`api_demo/`**: A simple Actix-based backend API (currently providing basic counter GET/POST/PUT endpoints). In the context of the ATProto distributed counter, this backend's role might evolve or be less central if the counter state is primarily managed via ATProto records. 22 + * **`app_demo/`**: The Yew frontend application (Rust compiled to Wasm). This is where the ATProto authentication and record creation logic will reside. 23 + * **`types_demo/`**: Shared data types between the frontend and backend, including lexicon definitions for any custom ATProto records (e.g., `dev.alternatebuild.rustDemo.count`). 24 + 25 + ## 3. AT Protocol Integration Strategy 26 + 27 + ### 3.1. Authentication 28 + * The `app_demo` frontend will use the `atrium-oauth` crate to handle OAuth 2.0 authentication against a user's PDS. 29 + * The flow will be: 30 + 1. User clicks "Login". 31 + 2. App redirects to the PDS authorization endpoint. 32 + 3. User approves. 33 + 4. PDS redirects back to an `/oauth/callback` route in our Yew app. 34 + 5. App exchanges the authorization code for an access token and session data. 35 + 6. Session data (including access and refresh tokens) will be stored securely in the browser's IndexedDB using helper functions and the `atrium_common::store::Store` trait. 36 + 37 + ### 3.2. Distributed Counter Record 38 + * **Lexicon:** A custom lexicon, `dev.alternatebuild.rustDemo.count`, will be defined. 39 + * `nsid`: `dev.alternatebuild.rustDemo.count` 40 + * `key`: `literal:self` (ensuring only one such record per repository, making it a singleton by design for this use case). 41 + * The record schema itself might be simple, e.g., containing a timestamp or a minimal "counted: true" field. The existence and timestamp of the record are the primary signals. 42 + * **Action:** When an authenticated user clicks "Increment Counter": 43 + 1. The `app_demo` frontend will use the `atrium-api` (via an authenticated `XrpcClient` or `Agent`) to make a `com.atproto.repo.createRecord` call. 44 + 2. This will create (or update, if `swapCommit` logic is used, though `createRecord` for a new `rkey` each time might be simpler if we only care about the *act* of counting) the `dev.alternatebuild.rustDemo.count` record in their repository. For a true singleton identified by `rkey: "self"`, we'd use `putRecord` to create or update. Given `key: "literal:self"` in the lexicon usually implies the `rkey` will be fixed (often also "self" or a predefined string), `putRecord` is more appropriate for creating/updating a singleton record at a known path. If the lexicon truly means any `rkey` is fine as long as the record name is `self` this implies a collection where only one item has the name self. *Correction*: the `key: "literal:self"` in the lexicon refers to the record's *name* within the collection, not the `rkey` itself. A common pattern for singletons is to use a fixed `rkey` like `"self"`. 45 + Let's assume we'll use `com.atproto.repo.putRecord` with `rkey: "self"` for this singleton. 46 + 47 + ### 3.3. (Future) Global Count Aggregation 48 + * To display a global count, the frontend could connect to a Bluesky Appview's firehose subscription endpoint (e.g., `com.atproto.sync.subscribeRepos`). 49 + * It would filter for `createRecord` or `putRecord` operations related to the `dev.alternatebuild.rustDemo.count` NSID. 50 + * The frontend would maintain a client-side aggregation of these records to display a live global count. This avoids a centralized counter backend. 51 + 52 + ## 4. Reference Repositories (Informational Only) 53 + 54 + Within the broader workspace (but **not** part of the `rust_demo_app` build itself), two repositories have been cloned for reference purposes. These are used to understand best practices, see `atrium-rs` usage examples, and borrow code patterns. They are **not** dependencies of `rust_demo_app` and are only present to be read from (e.g., using `rg` - ripgrep) during development. 55 + 56 + * **`atrium/`**: A clone of the main [`atrium-rs/atrium`](https://github.com/atrium-rs/atrium) repository. This provides access to the source code of the SDK crates themselves (`atrium-api`, `atrium-oauth`, `atrium-common`, etc.) and internal examples or tools like `lexgen`. 57 + * **`at_2048/`**: A clone of the [`fatfingers23/at_2048`](https://github.com/fatfingers23/at_2048) project. This is a more complete ATProto-enabled web application (a 2048 game) built with Yew and `atrium-rs`. It serves as an excellent practical example for: 58 + * OAuth flow implementation in Yew. 59 + * `XrpcClient` and `Agent` usage. 60 + * Session storage in IndexedDB. 61 + * Lexicon usage and record manipulation. 62 + * Yew application structure with ATProto. 63 + 64 + These repositories are invaluable for accelerating development and ensuring correct usage of the `atrium-rs` ecosystem.
+5
rust_demo_app/app_demo/Cargo.toml
··· 44 44 serde-wasm-bindgen = "0.6.5" 45 45 gloo-net = "=0.5.0" 46 46 wasm-bindgen-futures = "=0.4.50" 47 + 48 + yew-router = "0.18.0" 49 + yewdux = "0.11.0" 50 + serde_qs = "0.12" 51 + serde_html_form = "0.2.7"
-820
rust_demo_app/app_demo/dist/app_demo-814a61e8321d189a.js
··· 1 - let wasm; 2 - 3 - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); 4 - 5 - if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; 6 - 7 - let cachedUint8ArrayMemory0 = null; 8 - 9 - function getUint8ArrayMemory0() { 10 - if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { 11 - cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); 12 - } 13 - return cachedUint8ArrayMemory0; 14 - } 15 - 16 - function getStringFromWasm0(ptr, len) { 17 - ptr = ptr >>> 0; 18 - return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); 19 - } 20 - 21 - function addToExternrefTable0(obj) { 22 - const idx = wasm.__externref_table_alloc(); 23 - wasm.__wbindgen_export_2.set(idx, obj); 24 - return idx; 25 - } 26 - 27 - function handleError(f, args) { 28 - try { 29 - return f.apply(this, args); 30 - } catch (e) { 31 - const idx = addToExternrefTable0(e); 32 - wasm.__wbindgen_exn_store(idx); 33 - } 34 - } 35 - 36 - function isLikeNone(x) { 37 - return x === undefined || x === null; 38 - } 39 - 40 - let cachedDataViewMemory0 = null; 41 - 42 - function getDataViewMemory0() { 43 - if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { 44 - cachedDataViewMemory0 = new DataView(wasm.memory.buffer); 45 - } 46 - return cachedDataViewMemory0; 47 - } 48 - 49 - function getArrayJsValueFromWasm0(ptr, len) { 50 - ptr = ptr >>> 0; 51 - const mem = getDataViewMemory0(); 52 - const result = []; 53 - for (let i = ptr; i < ptr + 4 * len; i += 4) { 54 - result.push(wasm.__wbindgen_export_2.get(mem.getUint32(i, true))); 55 - } 56 - wasm.__externref_drop_slice(ptr, len); 57 - return result; 58 - } 59 - 60 - let WASM_VECTOR_LEN = 0; 61 - 62 - const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); 63 - 64 - const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' 65 - ? function (arg, view) { 66 - return cachedTextEncoder.encodeInto(arg, view); 67 - } 68 - : function (arg, view) { 69 - const buf = cachedTextEncoder.encode(arg); 70 - view.set(buf); 71 - return { 72 - read: arg.length, 73 - written: buf.length 74 - }; 75 - }); 76 - 77 - function passStringToWasm0(arg, malloc, realloc) { 78 - 79 - if (realloc === undefined) { 80 - const buf = cachedTextEncoder.encode(arg); 81 - const ptr = malloc(buf.length, 1) >>> 0; 82 - getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); 83 - WASM_VECTOR_LEN = buf.length; 84 - return ptr; 85 - } 86 - 87 - let len = arg.length; 88 - let ptr = malloc(len, 1) >>> 0; 89 - 90 - const mem = getUint8ArrayMemory0(); 91 - 92 - let offset = 0; 93 - 94 - for (; offset < len; offset++) { 95 - const code = arg.charCodeAt(offset); 96 - if (code > 0x7F) break; 97 - mem[ptr + offset] = code; 98 - } 99 - 100 - if (offset !== len) { 101 - if (offset !== 0) { 102 - arg = arg.slice(offset); 103 - } 104 - ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; 105 - const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); 106 - const ret = encodeString(arg, view); 107 - 108 - offset += ret.written; 109 - ptr = realloc(ptr, len, offset, 1) >>> 0; 110 - } 111 - 112 - WASM_VECTOR_LEN = offset; 113 - return ptr; 114 - } 115 - 116 - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') 117 - ? { register: () => {}, unregister: () => {} } 118 - : new FinalizationRegistry(state => { 119 - wasm.__wbindgen_export_7.get(state.dtor)(state.a, state.b) 120 - }); 121 - 122 - function makeClosure(arg0, arg1, dtor, f) { 123 - const state = { a: arg0, b: arg1, cnt: 1, dtor }; 124 - const real = (...args) => { 125 - // First up with a closure we increment the internal reference 126 - // count. This ensures that the Rust closure environment won't 127 - // be deallocated while we're invoking it. 128 - state.cnt++; 129 - try { 130 - return f(state.a, state.b, ...args); 131 - } finally { 132 - if (--state.cnt === 0) { 133 - wasm.__wbindgen_export_7.get(state.dtor)(state.a, state.b); 134 - state.a = 0; 135 - CLOSURE_DTORS.unregister(state); 136 - } 137 - } 138 - }; 139 - real.original = state; 140 - CLOSURE_DTORS.register(real, state, state); 141 - return real; 142 - } 143 - 144 - function makeMutClosure(arg0, arg1, dtor, f) { 145 - const state = { a: arg0, b: arg1, cnt: 1, dtor }; 146 - const real = (...args) => { 147 - // First up with a closure we increment the internal reference 148 - // count. This ensures that the Rust closure environment won't 149 - // be deallocated while we're invoking it. 150 - state.cnt++; 151 - const a = state.a; 152 - state.a = 0; 153 - try { 154 - return f(a, state.b, ...args); 155 - } finally { 156 - if (--state.cnt === 0) { 157 - wasm.__wbindgen_export_7.get(state.dtor)(a, state.b); 158 - CLOSURE_DTORS.unregister(state); 159 - } else { 160 - state.a = a; 161 - } 162 - } 163 - }; 164 - real.original = state; 165 - CLOSURE_DTORS.register(real, state, state); 166 - return real; 167 - } 168 - 169 - function debugString(val) { 170 - // primitive types 171 - const type = typeof val; 172 - if (type == 'number' || type == 'boolean' || val == null) { 173 - return `${val}`; 174 - } 175 - if (type == 'string') { 176 - return `"${val}"`; 177 - } 178 - if (type == 'symbol') { 179 - const description = val.description; 180 - if (description == null) { 181 - return 'Symbol'; 182 - } else { 183 - return `Symbol(${description})`; 184 - } 185 - } 186 - if (type == 'function') { 187 - const name = val.name; 188 - if (typeof name == 'string' && name.length > 0) { 189 - return `Function(${name})`; 190 - } else { 191 - return 'Function'; 192 - } 193 - } 194 - // objects 195 - if (Array.isArray(val)) { 196 - const length = val.length; 197 - let debug = '['; 198 - if (length > 0) { 199 - debug += debugString(val[0]); 200 - } 201 - for(let i = 1; i < length; i++) { 202 - debug += ', ' + debugString(val[i]); 203 - } 204 - debug += ']'; 205 - return debug; 206 - } 207 - // Test for built-in 208 - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); 209 - let className; 210 - if (builtInMatches && builtInMatches.length > 1) { 211 - className = builtInMatches[1]; 212 - } else { 213 - // Failed to match the standard '[object ClassName]' 214 - return toString.call(val); 215 - } 216 - if (className == 'Object') { 217 - // we're a user defined class or Object 218 - // JSON.stringify avoids problems with cycles, and is generally much 219 - // easier than looping through ownProperties of `val`. 220 - try { 221 - return 'Object(' + JSON.stringify(val) + ')'; 222 - } catch (_) { 223 - return 'Object'; 224 - } 225 - } 226 - // errors 227 - if (val instanceof Error) { 228 - return `${val.name}: ${val.message}\n${val.stack}`; 229 - } 230 - // TODO we could test for more things here, like `Set`s and `Map`s. 231 - return className; 232 - } 233 - function __wbg_adapter_22(arg0, arg1, arg2) { 234 - wasm.closure147_externref_shim(arg0, arg1, arg2); 235 - } 236 - 237 - function __wbg_adapter_25(arg0, arg1, arg2) { 238 - wasm.closure241_externref_shim(arg0, arg1, arg2); 239 - } 240 - 241 - async function __wbg_load(module, imports) { 242 - if (typeof Response === 'function' && module instanceof Response) { 243 - if (typeof WebAssembly.instantiateStreaming === 'function') { 244 - try { 245 - return await WebAssembly.instantiateStreaming(module, imports); 246 - 247 - } catch (e) { 248 - if (module.headers.get('Content-Type') != 'application/wasm') { 249 - console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); 250 - 251 - } else { 252 - throw e; 253 - } 254 - } 255 - } 256 - 257 - const bytes = await module.arrayBuffer(); 258 - return await WebAssembly.instantiate(bytes, imports); 259 - 260 - } else { 261 - const instance = await WebAssembly.instantiate(module, imports); 262 - 263 - if (instance instanceof WebAssembly.Instance) { 264 - return { instance, module }; 265 - 266 - } else { 267 - return instance; 268 - } 269 - } 270 - } 271 - 272 - function __wbg_get_imports() { 273 - const imports = {}; 274 - imports.wbg = {}; 275 - imports.wbg.__wbg_addEventListener_84ae3eac6e15480a = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { 276 - arg0.addEventListener(getStringFromWasm0(arg1, arg2), arg3, arg4); 277 - }, arguments) }; 278 - imports.wbg.__wbg_body_942ea927546a04ba = function(arg0) { 279 - const ret = arg0.body; 280 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 281 - }; 282 - imports.wbg.__wbg_bubbles_afd8dd1d14b05aba = function(arg0) { 283 - const ret = arg0.bubbles; 284 - return ret; 285 - }; 286 - imports.wbg.__wbg_cachekey_57601dac16343711 = function(arg0) { 287 - const ret = arg0.__yew_subtree_cache_key; 288 - return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0; 289 - }; 290 - imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) { 291 - const ret = arg0.call(arg1); 292 - return ret; 293 - }, arguments) }; 294 - imports.wbg.__wbg_cancelBubble_2e66f509cdea4d7e = function(arg0) { 295 - const ret = arg0.cancelBubble; 296 - return ret; 297 - }; 298 - imports.wbg.__wbg_childNodes_c4423003f3a9441f = function(arg0) { 299 - const ret = arg0.childNodes; 300 - return ret; 301 - }; 302 - imports.wbg.__wbg_cloneNode_e35b333b87d51340 = function() { return handleError(function (arg0) { 303 - const ret = arg0.cloneNode(); 304 - return ret; 305 - }, arguments) }; 306 - imports.wbg.__wbg_composedPath_977ce97a0ef39358 = function(arg0) { 307 - const ret = arg0.composedPath(); 308 - return ret; 309 - }; 310 - imports.wbg.__wbg_createElementNS_914d752e521987da = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { 311 - const ret = arg0.createElementNS(arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); 312 - return ret; 313 - }, arguments) }; 314 - imports.wbg.__wbg_createElement_8c9931a732ee2fea = function() { return handleError(function (arg0, arg1, arg2) { 315 - const ret = arg0.createElement(getStringFromWasm0(arg1, arg2)); 316 - return ret; 317 - }, arguments) }; 318 - imports.wbg.__wbg_createTextNode_42af1a9f21bb3360 = function(arg0, arg1, arg2) { 319 - const ret = arg0.createTextNode(getStringFromWasm0(arg1, arg2)); 320 - return ret; 321 - }; 322 - imports.wbg.__wbg_debug_e17b51583ca6a632 = function(arg0, arg1, arg2, arg3) { 323 - console.debug(arg0, arg1, arg2, arg3); 324 - }; 325 - imports.wbg.__wbg_document_d249400bd7bd996d = function(arg0) { 326 - const ret = arg0.document; 327 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 328 - }; 329 - imports.wbg.__wbg_error_3c7d958458bf649b = function(arg0, arg1) { 330 - var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice(); 331 - wasm.__wbindgen_free(arg0, arg1 * 4, 4); 332 - console.error(...v0); 333 - }; 334 - imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) { 335 - console.error(arg0); 336 - }; 337 - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { 338 - let deferred0_0; 339 - let deferred0_1; 340 - try { 341 - deferred0_0 = arg0; 342 - deferred0_1 = arg1; 343 - console.error(getStringFromWasm0(arg0, arg1)); 344 - } finally { 345 - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); 346 - } 347 - }; 348 - imports.wbg.__wbg_error_80de38b3f7cc3c3c = function(arg0, arg1, arg2, arg3) { 349 - console.error(arg0, arg1, arg2, arg3); 350 - }; 351 - imports.wbg.__wbg_fetch_509096533071c657 = function(arg0, arg1) { 352 - const ret = arg0.fetch(arg1); 353 - return ret; 354 - }; 355 - imports.wbg.__wbg_fetch_b7bf320f681242d2 = function(arg0, arg1) { 356 - const ret = arg0.fetch(arg1); 357 - return ret; 358 - }; 359 - imports.wbg.__wbg_from_2a5d3e218e67aa85 = function(arg0) { 360 - const ret = Array.from(arg0); 361 - return ret; 362 - }; 363 - imports.wbg.__wbg_get_67b2ba62fc30de12 = function() { return handleError(function (arg0, arg1) { 364 - const ret = Reflect.get(arg0, arg1); 365 - return ret; 366 - }, arguments) }; 367 - imports.wbg.__wbg_get_b9b93047fe3cf45b = function(arg0, arg1) { 368 - const ret = arg0[arg1 >>> 0]; 369 - return ret; 370 - }; 371 - imports.wbg.__wbg_host_166cb082dae71d08 = function(arg0) { 372 - const ret = arg0.host; 373 - return ret; 374 - }; 375 - imports.wbg.__wbg_info_033d8b8a0838f1d3 = function(arg0, arg1, arg2, arg3) { 376 - console.info(arg0, arg1, arg2, arg3); 377 - }; 378 - imports.wbg.__wbg_insertBefore_c181fb91844cd959 = function() { return handleError(function (arg0, arg1, arg2) { 379 - const ret = arg0.insertBefore(arg1, arg2); 380 - return ret; 381 - }, arguments) }; 382 - imports.wbg.__wbg_instanceof_Element_0af65443936d5154 = function(arg0) { 383 - let result; 384 - try { 385 - result = arg0 instanceof Element; 386 - } catch (_) { 387 - result = false; 388 - } 389 - const ret = result; 390 - return ret; 391 - }; 392 - imports.wbg.__wbg_instanceof_Error_4d54113b22d20306 = function(arg0) { 393 - let result; 394 - try { 395 - result = arg0 instanceof Error; 396 - } catch (_) { 397 - result = false; 398 - } 399 - const ret = result; 400 - return ret; 401 - }; 402 - imports.wbg.__wbg_instanceof_Response_f2cc20d9f7dfd644 = function(arg0) { 403 - let result; 404 - try { 405 - result = arg0 instanceof Response; 406 - } catch (_) { 407 - result = false; 408 - } 409 - const ret = result; 410 - return ret; 411 - }; 412 - imports.wbg.__wbg_instanceof_ShadowRoot_726578bcd7fa418a = function(arg0) { 413 - let result; 414 - try { 415 - result = arg0 instanceof ShadowRoot; 416 - } catch (_) { 417 - result = false; 418 - } 419 - const ret = result; 420 - return ret; 421 - }; 422 - imports.wbg.__wbg_instanceof_Window_def73ea0955fc569 = function(arg0) { 423 - let result; 424 - try { 425 - result = arg0 instanceof Window; 426 - } catch (_) { 427 - result = false; 428 - } 429 - const ret = result; 430 - return ret; 431 - }; 432 - imports.wbg.__wbg_instanceof_WorkerGlobalScope_dbdbdea7e3b56493 = function(arg0) { 433 - let result; 434 - try { 435 - result = arg0 instanceof WorkerGlobalScope; 436 - } catch (_) { 437 - result = false; 438 - } 439 - const ret = result; 440 - return ret; 441 - }; 442 - imports.wbg.__wbg_is_c7481c65e7e5df9e = function(arg0, arg1) { 443 - const ret = Object.is(arg0, arg1); 444 - return ret; 445 - }; 446 - imports.wbg.__wbg_lastChild_e20d4dc0f9e02ce7 = function(arg0) { 447 - const ret = arg0.lastChild; 448 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 449 - }; 450 - imports.wbg.__wbg_length_e2d2a49132c1b256 = function(arg0) { 451 - const ret = arg0.length; 452 - return ret; 453 - }; 454 - imports.wbg.__wbg_listenerid_ed1678830a5b97ec = function(arg0) { 455 - const ret = arg0.__yew_listener_id; 456 - return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0; 457 - }; 458 - imports.wbg.__wbg_log_cad59bb680daec67 = function(arg0, arg1, arg2, arg3) { 459 - console.log(arg0, arg1, arg2, arg3); 460 - }; 461 - imports.wbg.__wbg_message_97a2af9b89d693a3 = function(arg0) { 462 - const ret = arg0.message; 463 - return ret; 464 - }; 465 - imports.wbg.__wbg_name_0b327d569f00ebee = function(arg0) { 466 - const ret = arg0.name; 467 - return ret; 468 - }; 469 - imports.wbg.__wbg_namespaceURI_63ddded7f2fdbe94 = function(arg0, arg1) { 470 - const ret = arg1.namespaceURI; 471 - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 472 - var len1 = WASM_VECTOR_LEN; 473 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 474 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 475 - }; 476 - imports.wbg.__wbg_new_018dcc2d6c8c2f6a = function() { return handleError(function () { 477 - const ret = new Headers(); 478 - return ret; 479 - }, arguments) }; 480 - imports.wbg.__wbg_new_405e22f390576ce2 = function() { 481 - const ret = new Object(); 482 - return ret; 483 - }; 484 - imports.wbg.__wbg_new_80bf4ee74f41ff92 = function() { return handleError(function () { 485 - const ret = new URLSearchParams(); 486 - return ret; 487 - }, arguments) }; 488 - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { 489 - const ret = new Error(); 490 - return ret; 491 - }; 492 - imports.wbg.__wbg_new_9ffbe0a71eff35e3 = function() { return handleError(function (arg0, arg1) { 493 - const ret = new URL(getStringFromWasm0(arg0, arg1)); 494 - return ret; 495 - }, arguments) }; 496 - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { 497 - const ret = new Function(getStringFromWasm0(arg0, arg1)); 498 - return ret; 499 - }; 500 - imports.wbg.__wbg_newwithstr_78e86e03c4ae814e = function() { return handleError(function (arg0, arg1) { 501 - const ret = new Request(getStringFromWasm0(arg0, arg1)); 502 - return ret; 503 - }, arguments) }; 504 - imports.wbg.__wbg_newwithstrandinit_06c535e0a867c635 = function() { return handleError(function (arg0, arg1, arg2) { 505 - const ret = new Request(getStringFromWasm0(arg0, arg1), arg2); 506 - return ret; 507 - }, arguments) }; 508 - imports.wbg.__wbg_nextSibling_f17f68d089a20939 = function(arg0) { 509 - const ret = arg0.nextSibling; 510 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 511 - }; 512 - imports.wbg.__wbg_ok_3aaf32d069979723 = function(arg0) { 513 - const ret = arg0.ok; 514 - return ret; 515 - }; 516 - imports.wbg.__wbg_outerHTML_69175e02bad1633b = function(arg0, arg1) { 517 - const ret = arg1.outerHTML; 518 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 519 - const len1 = WASM_VECTOR_LEN; 520 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 521 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 522 - }; 523 - imports.wbg.__wbg_parentElement_be28a1a931f9c9b7 = function(arg0) { 524 - const ret = arg0.parentElement; 525 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 526 - }; 527 - imports.wbg.__wbg_parentNode_9de97a0e7973ea4e = function(arg0) { 528 - const ret = arg0.parentNode; 529 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 530 - }; 531 - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { 532 - queueMicrotask(arg0); 533 - }; 534 - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { 535 - const ret = arg0.queueMicrotask; 536 - return ret; 537 - }; 538 - imports.wbg.__wbg_removeAttribute_e419cd6726b4c62f = function() { return handleError(function (arg0, arg1, arg2) { 539 - arg0.removeAttribute(getStringFromWasm0(arg1, arg2)); 540 - }, arguments) }; 541 - imports.wbg.__wbg_removeChild_841bf1dc802c0a2c = function() { return handleError(function (arg0, arg1) { 542 - const ret = arg0.removeChild(arg1); 543 - return ret; 544 - }, arguments) }; 545 - imports.wbg.__wbg_removeEventListener_d365ee1c2a7b08f0 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { 546 - arg0.removeEventListener(getStringFromWasm0(arg1, arg2), arg3, arg4 !== 0); 547 - }, arguments) }; 548 - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { 549 - const ret = Promise.resolve(arg0); 550 - return ret; 551 - }; 552 - imports.wbg.__wbg_search_e0e79cfe010c5c23 = function(arg0, arg1) { 553 - const ret = arg1.search; 554 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 555 - const len1 = WASM_VECTOR_LEN; 556 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 557 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 558 - }; 559 - imports.wbg.__wbg_setAttribute_2704501201f15687 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { 560 - arg0.setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); 561 - }, arguments) }; 562 - imports.wbg.__wbg_set_bb8cecf6a62b9f46 = function() { return handleError(function (arg0, arg1, arg2) { 563 - const ret = Reflect.set(arg0, arg1, arg2); 564 - return ret; 565 - }, arguments) }; 566 - imports.wbg.__wbg_setcachekey_bb5f908a0e3ee714 = function(arg0, arg1) { 567 - arg0.__yew_subtree_cache_key = arg1 >>> 0; 568 - }; 569 - imports.wbg.__wbg_setcapture_46bd7043887eba02 = function(arg0, arg1) { 570 - arg0.capture = arg1 !== 0; 571 - }; 572 - imports.wbg.__wbg_setchecked_5024c3767a6970c2 = function(arg0, arg1) { 573 - arg0.checked = arg1 !== 0; 574 - }; 575 - imports.wbg.__wbg_setheaders_834c0bdb6a8949ad = function(arg0, arg1) { 576 - arg0.headers = arg1; 577 - }; 578 - imports.wbg.__wbg_setinnerHTML_31bde41f835786f7 = function(arg0, arg1, arg2) { 579 - arg0.innerHTML = getStringFromWasm0(arg1, arg2); 580 - }; 581 - imports.wbg.__wbg_setlistenerid_3d14d37a42484593 = function(arg0, arg1) { 582 - arg0.__yew_listener_id = arg1 >>> 0; 583 - }; 584 - imports.wbg.__wbg_setmethod_3c5280fe5d890842 = function(arg0, arg1, arg2) { 585 - arg0.method = getStringFromWasm0(arg1, arg2); 586 - }; 587 - imports.wbg.__wbg_setnodeValue_58cb1b2f6b6c33d2 = function(arg0, arg1, arg2) { 588 - arg0.nodeValue = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2); 589 - }; 590 - imports.wbg.__wbg_setpassive_57a5a4c4b00a7c62 = function(arg0, arg1) { 591 - arg0.passive = arg1 !== 0; 592 - }; 593 - imports.wbg.__wbg_setsearch_609451e9e712f3c6 = function(arg0, arg1, arg2) { 594 - arg0.search = getStringFromWasm0(arg1, arg2); 595 - }; 596 - imports.wbg.__wbg_setsubtreeid_32b8ceff55862e29 = function(arg0, arg1) { 597 - arg0.__yew_subtree_id = arg1 >>> 0; 598 - }; 599 - imports.wbg.__wbg_setvalue_08d17a42e5d5069d = function(arg0, arg1, arg2) { 600 - arg0.value = getStringFromWasm0(arg1, arg2); 601 - }; 602 - imports.wbg.__wbg_setvalue_6ad9ef6c692ea746 = function(arg0, arg1, arg2) { 603 - arg0.value = getStringFromWasm0(arg1, arg2); 604 - }; 605 - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { 606 - const ret = arg1.stack; 607 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 608 - const len1 = WASM_VECTOR_LEN; 609 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 610 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 611 - }; 612 - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { 613 - const ret = typeof global === 'undefined' ? null : global; 614 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 615 - }; 616 - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { 617 - const ret = typeof globalThis === 'undefined' ? null : globalThis; 618 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 619 - }; 620 - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { 621 - const ret = typeof self === 'undefined' ? null : self; 622 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 623 - }; 624 - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { 625 - const ret = typeof window === 'undefined' ? null : window; 626 - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); 627 - }; 628 - imports.wbg.__wbg_statusText_207754230b39e67c = function(arg0, arg1) { 629 - const ret = arg1.statusText; 630 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 631 - const len1 = WASM_VECTOR_LEN; 632 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 633 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 634 - }; 635 - imports.wbg.__wbg_subtreeid_e65dfcc52d403fd9 = function(arg0) { 636 - const ret = arg0.__yew_subtree_id; 637 - return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0; 638 - }; 639 - imports.wbg.__wbg_textContent_215d0f87d539368a = function(arg0, arg1) { 640 - const ret = arg1.textContent; 641 - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 642 - var len1 = WASM_VECTOR_LEN; 643 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 644 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 645 - }; 646 - imports.wbg.__wbg_text_7805bea50de2af49 = function() { return handleError(function (arg0) { 647 - const ret = arg0.text(); 648 - return ret; 649 - }, arguments) }; 650 - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { 651 - const ret = arg0.then(arg1); 652 - return ret; 653 - }; 654 - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { 655 - const ret = arg0.then(arg1, arg2); 656 - return ret; 657 - }; 658 - imports.wbg.__wbg_toString_5285597960676b7b = function(arg0) { 659 - const ret = arg0.toString(); 660 - return ret; 661 - }; 662 - imports.wbg.__wbg_toString_c813bbd34d063839 = function(arg0) { 663 - const ret = arg0.toString(); 664 - return ret; 665 - }; 666 - imports.wbg.__wbg_url_8f9653b899456042 = function(arg0, arg1) { 667 - const ret = arg1.url; 668 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 669 - const len1 = WASM_VECTOR_LEN; 670 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 671 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 672 - }; 673 - imports.wbg.__wbg_value_1d971aac958c6f2f = function(arg0, arg1) { 674 - const ret = arg1.value; 675 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 676 - const len1 = WASM_VECTOR_LEN; 677 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 678 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 679 - }; 680 - imports.wbg.__wbg_value_91cbf0dd3ab84c1e = function(arg0, arg1) { 681 - const ret = arg1.value; 682 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 683 - const len1 = WASM_VECTOR_LEN; 684 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 685 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 686 - }; 687 - imports.wbg.__wbg_warn_aaf1f4664a035bd6 = function(arg0, arg1, arg2, arg3) { 688 - console.warn(arg0, arg1, arg2, arg3); 689 - }; 690 - imports.wbg.__wbindgen_cb_drop = function(arg0) { 691 - const obj = arg0.original; 692 - if (obj.cnt-- == 1) { 693 - obj.a = 0; 694 - return true; 695 - } 696 - const ret = false; 697 - return ret; 698 - }; 699 - imports.wbg.__wbindgen_closure_wrapper1517 = function(arg0, arg1, arg2) { 700 - const ret = makeClosure(arg0, arg1, 148, __wbg_adapter_22); 701 - return ret; 702 - }; 703 - imports.wbg.__wbindgen_closure_wrapper3717 = function(arg0, arg1, arg2) { 704 - const ret = makeMutClosure(arg0, arg1, 242, __wbg_adapter_25); 705 - return ret; 706 - }; 707 - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { 708 - const ret = debugString(arg1); 709 - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 710 - const len1 = WASM_VECTOR_LEN; 711 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 712 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 713 - }; 714 - imports.wbg.__wbindgen_init_externref_table = function() { 715 - const table = wasm.__wbindgen_export_2; 716 - const offset = table.grow(4); 717 - table.set(0, undefined); 718 - table.set(offset + 0, undefined); 719 - table.set(offset + 1, null); 720 - table.set(offset + 2, true); 721 - table.set(offset + 3, false); 722 - ; 723 - }; 724 - imports.wbg.__wbindgen_is_function = function(arg0) { 725 - const ret = typeof(arg0) === 'function'; 726 - return ret; 727 - }; 728 - imports.wbg.__wbindgen_is_undefined = function(arg0) { 729 - const ret = arg0 === undefined; 730 - return ret; 731 - }; 732 - imports.wbg.__wbindgen_string_get = function(arg0, arg1) { 733 - const obj = arg1; 734 - const ret = typeof(obj) === 'string' ? obj : undefined; 735 - var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 736 - var len1 = WASM_VECTOR_LEN; 737 - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); 738 - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); 739 - }; 740 - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { 741 - const ret = getStringFromWasm0(arg0, arg1); 742 - return ret; 743 - }; 744 - imports.wbg.__wbindgen_throw = function(arg0, arg1) { 745 - throw new Error(getStringFromWasm0(arg0, arg1)); 746 - }; 747 - 748 - return imports; 749 - } 750 - 751 - function __wbg_init_memory(imports, memory) { 752 - 753 - } 754 - 755 - function __wbg_finalize_init(instance, module) { 756 - wasm = instance.exports; 757 - __wbg_init.__wbindgen_wasm_module = module; 758 - cachedDataViewMemory0 = null; 759 - cachedUint8ArrayMemory0 = null; 760 - 761 - 762 - wasm.__wbindgen_start(); 763 - return wasm; 764 - } 765 - 766 - function initSync(module) { 767 - if (wasm !== undefined) return wasm; 768 - 769 - 770 - if (typeof module !== 'undefined') { 771 - if (Object.getPrototypeOf(module) === Object.prototype) { 772 - ({module} = module) 773 - } else { 774 - console.warn('using deprecated parameters for `initSync()`; pass a single object instead') 775 - } 776 - } 777 - 778 - const imports = __wbg_get_imports(); 779 - 780 - __wbg_init_memory(imports); 781 - 782 - if (!(module instanceof WebAssembly.Module)) { 783 - module = new WebAssembly.Module(module); 784 - } 785 - 786 - const instance = new WebAssembly.Instance(module, imports); 787 - 788 - return __wbg_finalize_init(instance, module); 789 - } 790 - 791 - async function __wbg_init(module_or_path) { 792 - if (wasm !== undefined) return wasm; 793 - 794 - 795 - if (typeof module_or_path !== 'undefined') { 796 - if (Object.getPrototypeOf(module_or_path) === Object.prototype) { 797 - ({module_or_path} = module_or_path) 798 - } else { 799 - console.warn('using deprecated parameters for the initialization function; pass a single object instead') 800 - } 801 - } 802 - 803 - if (typeof module_or_path === 'undefined') { 804 - module_or_path = new URL('app_demo_bg.wasm', import.meta.url); 805 - } 806 - const imports = __wbg_get_imports(); 807 - 808 - if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { 809 - module_or_path = fetch(module_or_path); 810 - } 811 - 812 - __wbg_init_memory(imports); 813 - 814 - const { instance, module } = await __wbg_load(await module_or_path, imports); 815 - 816 - return __wbg_finalize_init(instance, module); 817 - } 818 - 819 - export { initSync }; 820 - export default __wbg_init;
rust_demo_app/app_demo/dist/app_demo-814a61e8321d189a_bg.wasm

This is a binary file and will not be displayed.

-155
rust_demo_app/app_demo/dist/index.html
··· 1 - <!DOCTYPE html> 2 - <html> 3 - 4 - <head> 5 - <meta charset="utf-8" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 - <title>Rust Counter Demo</title> 8 - <link rel="stylesheet" href="/styles-1710c094e1b1c697.css" integrity="sha384-SVCA9/fxaw0hSXsKWVyzm4qWX7BBmBapjqORc/QpnRRRQF1rvYedexH0fPZ7Asjo"/> 9 - <link rel="modulepreload" href="/app_demo-814a61e8321d189a.js" crossorigin="anonymous" integrity="sha384-17HNJnOlv+fhkn8qgl+DdcrDmKclle4eSfXVqtwHwRXnusAgAOd62U7tvU72+w7o"><link rel="preload" href="/app_demo-814a61e8321d189a_bg.wasm" crossorigin="anonymous" integrity="sha384-7xMFYwhxnoMiFToa6HonKzQ/qhX383B/UqRDZ/Tf3SPcyPsa5a3FXNt8FKVHIH0D" as="fetch" type="application/wasm"></head> 10 - 11 - <body> 12 - <!-- Yew app will mount here --> 13 - 14 - <script type="module"> 15 - import init, * as bindings from '/app_demo-814a61e8321d189a.js'; 16 - const wasm = await init({ module_or_path: '/app_demo-814a61e8321d189a_bg.wasm' }); 17 - 18 - 19 - window.wasmBindings = bindings; 20 - 21 - 22 - dispatchEvent(new CustomEvent("TrunkApplicationStarted", {detail: {wasm}})); 23 - 24 - </script><script>"use strict"; 25 - 26 - (function () { 27 - 28 - const address = '{{__TRUNK_ADDRESS__}}'; 29 - const base = '{{__TRUNK_WS_BASE__}}'; 30 - let protocol = ''; 31 - protocol = 32 - protocol 33 - ? protocol 34 - : window.location.protocol === 'https:' 35 - ? 'wss' 36 - : 'ws'; 37 - const url = protocol + '://' + address + base + '.well-known/trunk/ws'; 38 - 39 - class Overlay { 40 - constructor() { 41 - // create an overlay 42 - this._overlay = document.createElement("div"); 43 - const style = this._overlay.style; 44 - style.height = "100vh"; 45 - style.width = "100vw"; 46 - style.position = "fixed"; 47 - style.top = "0"; 48 - style.left = "0"; 49 - style.backgroundColor = "rgba(222, 222, 222, 0.5)"; 50 - style.fontFamily = "sans-serif"; 51 - // not sure that's the right approach 52 - style.zIndex = "1000000"; 53 - style.backdropFilter = "blur(1rem)"; 54 - 55 - const container = document.createElement("div"); 56 - // center it 57 - container.style.position = "absolute"; 58 - container.style.top = "30%"; 59 - container.style.left = "15%"; 60 - container.style.maxWidth = "85%"; 61 - 62 - this._title = document.createElement("div"); 63 - this._title.innerText = "Build failure"; 64 - this._title.style.paddingBottom = "2rem"; 65 - this._title.style.fontSize = "2.5rem"; 66 - 67 - this._message = document.createElement("div"); 68 - this._message.style.whiteSpace = "pre-wrap"; 69 - 70 - const icon= document.createElement("div"); 71 - icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="#dc3545" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>'; 72 - this._title.prepend(icon); 73 - 74 - container.append(this._title, this._message); 75 - this._overlay.append(container); 76 - 77 - this._inject(); 78 - window.setInterval(() => { 79 - this._inject(); 80 - }, 250); 81 - } 82 - 83 - set reason(reason) { 84 - this._message.textContent = reason; 85 - } 86 - 87 - _inject() { 88 - if (!this._overlay.isConnected) { 89 - // prepend it 90 - document.body?.prepend(this._overlay); 91 - } 92 - } 93 - 94 - } 95 - 96 - class Client { 97 - constructor(url) { 98 - this.url = url; 99 - this.poll_interval = 5000; 100 - this._overlay = null; 101 - } 102 - 103 - start() { 104 - const ws = new WebSocket(this.url); 105 - ws.onmessage = (ev) => { 106 - const msg = JSON.parse(ev.data); 107 - switch (msg.type) { 108 - case "reload": 109 - this.reload(); 110 - break; 111 - case "buildFailure": 112 - this.buildFailure(msg.data) 113 - break; 114 - } 115 - }; 116 - ws.onclose = () => this.onclose(); 117 - } 118 - 119 - onclose() { 120 - window.setTimeout( 121 - () => { 122 - // when we successfully reconnect, we'll force a 123 - // reload (since we presumably lost connection to 124 - // trunk due to it being killed, so it will have 125 - // rebuilt on restart) 126 - const ws = new WebSocket(this.url); 127 - ws.onopen = () => window.location.reload(); 128 - ws.onclose = () => this.onclose(); 129 - }, 130 - this.poll_interval); 131 - } 132 - 133 - reload() { 134 - window.location.reload(); 135 - } 136 - 137 - buildFailure({reason}) { 138 - // also log the console 139 - console.error("Build failed:", reason); 140 - 141 - console.debug("Overlay", this._overlay); 142 - 143 - if (!this._overlay) { 144 - this._overlay = new Overlay(); 145 - } 146 - this._overlay.reason = reason; 147 - } 148 - } 149 - 150 - new Client(url).start(); 151 - 152 - })() 153 - </script></body> 154 - 155 - </html>
-102
rust_demo_app/app_demo/dist/styles-1710c094e1b1c697.css
··· 1 - @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&display=swap'); 2 - 3 - :root { 4 - --primary-color: #5c6bc0; 5 - --secondary-color: #8e99f3; 6 - --bg-color: #f5f5f5; 7 - --text-color: #333; 8 - --success-color: #4caf50; 9 - --error-color: #f44336; 10 - --font-mono: 'Fira Code', monospace; 11 - } 12 - 13 - body { 14 - font-family: var(--font-mono); 15 - background-color: var(--bg-color); 16 - color: var(--text-color); 17 - display: flex; 18 - justify-content: center; 19 - align-items: center; 20 - min-height: 100vh; 21 - margin: 0; 22 - padding: 20px; 23 - } 24 - 25 - .counter-app { 26 - background-color: white; 27 - border-radius: 12px; 28 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 29 - padding: 2rem; 30 - width: 100%; 31 - max-width: 500px; 32 - text-align: center; 33 - } 34 - 35 - h1 { 36 - color: var(--primary-color); 37 - margin-bottom: 1.5rem; 38 - font-family: var(--font-mono); 39 - font-weight: 600; 40 - letter-spacing: -0.5px; 41 - } 42 - 43 - .counter-value { 44 - font-size: 2rem; 45 - font-weight: 600; 46 - margin: 2rem 0; 47 - font-family: var(--font-mono); 48 - letter-spacing: -0.5px; 49 - } 50 - 51 - .counter-value.loading { 52 - color: var(--secondary-color); 53 - } 54 - 55 - .error-message { 56 - color: var(--error-color); 57 - background-color: rgba(244, 67, 54, 0.1); 58 - border-radius: 4px; 59 - padding: 0.75rem; 60 - margin: 1rem 0; 61 - font-family: var(--font-mono); 62 - } 63 - 64 - .button-group { 65 - display: flex; 66 - gap: 1rem; 67 - justify-content: center; 68 - margin-top: 2rem; 69 - } 70 - 71 - button { 72 - background-color: var(--primary-color); 73 - border: none; 74 - border-radius: 4px; 75 - color: white; 76 - cursor: pointer; 77 - font-size: 1rem; 78 - font-weight: 500; 79 - padding: 0.75rem 1.5rem; 80 - transition: all 0.2s; 81 - font-family: var(--font-mono); 82 - letter-spacing: 0.5px; 83 - } 84 - 85 - button:hover { 86 - background-color: var(--secondary-color); 87 - transform: translateY(-2px); 88 - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); 89 - } 90 - 91 - button:active { 92 - transform: translateY(0); 93 - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 94 - } 95 - 96 - button.reset { 97 - background-color: var(--error-color); 98 - } 99 - 100 - button.reset:hover { 101 - background-color: #e53935; 102 - }
+101 -45
rust_demo_app/app_demo/src/main.rs
··· 5 5 use types_demo::CounterState; 6 6 use wasm_bindgen_futures::spawn_local; 7 7 use yew::prelude::*; 8 + use yew_router::prelude::*; 9 + use yewdux::prelude::*; 10 + 11 + // Import the Store trait 12 + use atrium_common::store::Store; 8 13 9 14 mod atrium_stores; 10 15 mod idb; 11 16 mod oauth_client; 17 + 18 + // Add pages module 19 + pub mod pages; 20 + 21 + // Import page components 22 + use crate::pages::callback::CallbackPage; 23 + use crate::pages::home::HomePage; 24 + use crate::pages::login::LoginPage; 25 + 26 + // Import oauth client 27 + use crate::oauth_client::get_oauth_client; 28 + use atrium_api::agent::Agent; 29 + use atrium_api::types::string::Did; 12 30 13 31 const API_ROOT: &str = ""; 14 32 ··· 66 84 } 67 85 } 68 86 87 + // Yewdux Store for Authentication State 88 + #[derive(Default, Clone, PartialEq, Store)] 89 + pub struct AuthStore { 90 + pub did: Option<Did>, // Store the user's DID 91 + // We could store the agent here too, but it's not directly serializable by default for Store 92 + // So we might need a transient way or reconstruct it. For now, just DID. 93 + // #[store(skip_serializing)] // If we were to store Agent here 94 + // pub agent: Option<Arc<Agent<crate::oauth_client::OAuthClientType>>>, // This might be complex due to agent's client type 95 + } 96 + 97 + // Define the routes 98 + #[derive(Clone, Routable, PartialEq)] 99 + pub enum AppRoute { 100 + #[at("/")] 101 + Home, 102 + #[at("/login")] 103 + Login, 104 + #[at("/oauth/callback")] 105 + Callback, 106 + #[not_found] 107 + #[at("/404")] 108 + NotFound, 109 + } 110 + 111 + fn switch(routes: AppRoute) -> Html { 112 + match routes { 113 + AppRoute::Home => html! { <HomePage /> }, 114 + AppRoute::Login => html! { <LoginPage /> }, 115 + AppRoute::Callback => html! { <CallbackPage /> }, 116 + AppRoute::NotFound => html! { <h1>{ "404 Not Found" }</h1> }, 117 + } 118 + } 119 + 69 120 #[function_component(App)] 70 - fn app() -> Html { 71 - let state = use_reducer(AppState::default); 121 + pub fn app() -> Html { 122 + let (auth_store, auth_dispatch) = use_store::<AuthStore>(); 123 + let navigator = use_navigator(); 72 124 73 - // Initial load effect 125 + // Effect for session restoration 74 126 { 75 - let state = state.clone(); 127 + let auth_dispatch = auth_dispatch.clone(); // auth_dispatch for the effect 128 + let auth_store_for_effect = auth_store.clone(); // auth_store for the effect 76 129 use_effect_with((), move |_| { 77 - state.dispatch(CounterAction::LoadCounter); 78 - fetch_counter(state); 130 + log::info!("App loaded. AuthStore DID: {:?}", auth_store_for_effect.did); 131 + // ... session restoration logic will go here ... 79 132 || () 80 133 }); 81 134 } 82 135 83 - // Define event handlers 84 - let on_reset_click = { 85 - let state = state.clone(); 136 + // Clone auth_store for the logout callback 137 + let auth_store_for_logout = auth_store.clone(); 138 + let on_logout = { 139 + let auth_dispatch = auth_dispatch.clone(); // auth_dispatch for logout 140 + let navigator = navigator.clone(); 141 + // auth_store_for_logout is already cloned above and will be captured by the closure 86 142 Callback::from(move |_| { 87 - state.dispatch(CounterAction::ResetRequested); 88 - reset_counter(state.clone()); 89 - }) 90 - }; 143 + let auth_dispatch = auth_dispatch.clone(); // further clone for spawn_local 144 + let navigator = navigator.clone(); // further clone for spawn_local 145 + let auth_store_captured = auth_store_for_logout.clone(); // clone for spawn_local 91 146 92 - let on_increment_click = { 93 - let state = state.clone(); 94 - Callback::from(move |_| { 95 - // Special handling for value 10 96 - if let Some(counter) = &state.counter { 97 - if counter.value == 10 { 98 - state.dispatch(CounterAction::ClearError); 99 - } 147 + if let Some(did_to_logout) = auth_store_captured.did.clone() { 148 + yew::platform::spawn_local(async move { 149 + let store = crate::atrium_stores::IndexDBSessionStore::new(); 150 + match store.del(&did_to_logout).await { 151 + Ok(_) => { 152 + log::info!("Session deleted from store for DID: {:?}", did_to_logout) 153 + } 154 + Err(e) => log::error!("Failed to delete session from store: {:?}", e), 155 + } 156 + auth_dispatch.set(AuthStore { did: None }); 157 + if let Some(nav) = &navigator { 158 + nav.push(&AppRoute::Home); 159 + } 160 + }); 100 161 } 101 - increment_counter(state.clone()); 102 162 }) 103 163 }; 104 164 105 - // Render counter display 106 - let counter_display = match &state.counter { 107 - Some(counter) => { 108 - html! { <p class="counter-value">{ format!("Current value: {}", counter.value) }</p> } 109 - } 110 - None => html! { <p class="counter-value loading">{ "Loading..." }</p> }, 111 - }; 112 - 113 - // Render error message if any 114 - let error_display = match &state.error { 115 - Some(err) => html! { <div class="error-message">{ format!("Error: {}", err) }</div> }, 116 - None => html! {<> </>}, 117 - }; 118 - 119 165 html! { 120 - <div class="counter-app"> 121 - <h1>{ "Rust Counter Demo" }</h1> 122 - { counter_display } 123 - { error_display } 124 - <div class="button-group"> 125 - <button class="reset" onclick={on_reset_click}>{ "Reset" }</button> 126 - <button onclick={on_increment_click}>{ "Increment" }</button> 127 - </div> 128 - </div> 166 + <BrowserRouter> 167 + <nav class="navbar"> 168 + <div class="navbar-start"> 169 + <Link<AppRoute> to={AppRoute::Home} classes="btn btn-ghost normal-case text-xl"> 170 + { "Rust ATProto Counter" } 171 + </Link<AppRoute>> 172 + </div> 173 + <div class="navbar-end"> 174 + if auth_store.did.is_some() { 175 + <button onclick={on_logout} class="btn btn-ghost">{ "Logout" }</button> 176 + } else { 177 + <Link<AppRoute> to={AppRoute::Login} classes="btn btn-ghost">{ "Login" }</Link<AppRoute>> 178 + } 179 + </div> 180 + </nav> 181 + <main class="container mx-auto p-4"> 182 + <Switch<AppRoute> render={switch} /> 183 + </main> 184 + </BrowserRouter> 129 185 } 130 186 } 131 187
+1
rust_demo_app/app_demo/src/oauth_client.rs
··· 131 131 redirect_uris: Some(vec![format!("{}/oauth/callback", origin)]), 132 132 scopes: Some(vec![ 133 133 Scope::Known(KnownScope::Atproto), // Full access to user's account 134 + Scope::Known(KnownScope::TransitionGeneric), // Added this scope 134 135 // Add other scopes if needed, e.g., for specific lexicons if ATProto supports granular scopes later 135 136 ]), 136 137 // Other fields like client_name, logo_uri can be added
+129
rust_demo_app/app_demo/src/pages/callback.rs
··· 1 + use wasm_bindgen_futures::spawn_local; 2 + use yew::prelude::*; 3 + use yew_router::prelude::*; 4 + use yewdux::prelude::*; 5 + 6 + use crate::oauth_client::get_oauth_client; 7 + use crate::{AppRoute, AuthStore}; // Assuming AppRoute and AuthStore are in lib.rs or main.rs and pub 8 + 9 + // Import Store trait for state_store.get() 10 + use atrium_common::store::Store; 11 + // Import for parsing query string into CallbackParams 12 + use atrium_oauth::CallbackParams; // Assuming this is the correct path for 0.1.1 13 + // We might need IndexDBStateStore if we manually delete the state after successful callback 14 + use crate::atrium_stores::IndexDBStateStore; 15 + // Import Agent to make authenticated calls 16 + use atrium_api::agent::Agent; 17 + 18 + #[function_component(CallbackPage)] 19 + pub fn callback_page() -> Html { 20 + let (_, auth_dispatch) = use_store::<AuthStore>(); 21 + let navigator = use_navigator().unwrap(); // Should always be available in a routed context 22 + let location = use_location().unwrap(); // For accessing query parameters 23 + 24 + let error_message = use_state(|| None::<String>); 25 + 26 + { 27 + let auth_dispatch = auth_dispatch.clone(); 28 + let navigator = navigator.clone(); 29 + let error_message = error_message.clone(); 30 + 31 + use_effect_with((location.query_str().to_string(),), move |(query_str,)| { 32 + let query_str = query_str.clone(); // Ensure query_str is owned for the async block 33 + let auth_dispatch = auth_dispatch.clone(); 34 + let navigator = navigator.clone(); 35 + let error_message = error_message.clone(); 36 + 37 + spawn_local(async move { 38 + let params: CallbackParams = match serde_html_form::from_str(&query_str) { 39 + Ok(p) => p, 40 + Err(e) => { 41 + log::error!("Failed to parse callback query parameters: {:?}", e); 42 + error_message 43 + .set(Some(format!("Error parsing callback parameters: {:?}", e))); 44 + // Optionally redirect to login or show error 45 + navigator.push(&AppRoute::Login); 46 + return; 47 + } 48 + }; 49 + 50 + // The `state` value within `params` will be used by `client.callback()` 51 + // to retrieve necessary data (like PKCE verifier and original PDS host) from the state_store. 52 + let state_from_query = params.state.clone(); // For potential manual deletion later 53 + 54 + let oauth_client = get_oauth_client().await; 55 + 56 + match oauth_client.callback(params).await { 57 + Ok((session_data, _optional_dpop_nonce)) => { 58 + log::info!("OAuth callback successful, creating Agent..."); 59 + 60 + // Create Agent from the session 61 + // Note: The concrete type of Agent might depend on how OAuthSession implements SessionManager 62 + // Or Agent::new might be flexible enough. 63 + let agent = Agent::new(session_data); 64 + 65 + // Get session info using the agent 66 + match agent.api.com.atproto.server.get_session().await { 67 + Ok(session_info) => { 68 + log::info!( 69 + "Successfully retrieved session info. DID: {:?}, Handle: {:?}", 70 + session_info.did, 71 + session_info.handle 72 + ); 73 + // Extract the DID from the getSession response 74 + let user_did = session_info.did.clone(); // Clone the Did 75 + 76 + // Store the cloned DID 77 + auth_dispatch.set(AuthStore { 78 + did: Some(user_did), 79 + }); // No need to clone again here 80 + 81 + // Attempt to delete the used state from the state store (keep this logic) 82 + let state_store = IndexDBStateStore::new(); 83 + if let Some(state_key_to_delete) = state_from_query { 84 + if let Err(e) = state_store.del(&state_key_to_delete).await { 85 + log::warn!( 86 + "Failed to delete state from store after use: {:?}", 87 + e 88 + ); 89 + } 90 + } else { 91 + log::warn!( 92 + "No state found in CallbackParams to delete from store." 93 + ); 94 + } 95 + navigator.push(&AppRoute::Home); 96 + } 97 + Err(e) => { 98 + log::error!( 99 + "Failed to get session info using agent after successful callback: {:?}", 100 + e 101 + ); 102 + error_message 103 + .set(Some(format!("Failed to verify session: {:?}", e))); 104 + navigator.push(&AppRoute::Login); 105 + } 106 + } 107 + } 108 + Err(e) => { 109 + log::error!("OAuth callback processing failed: {:?}", e); 110 + error_message.set(Some(format!("Login failed during callback: {:?}", e))); 111 + navigator.push(&AppRoute::Login); 112 + } 113 + } 114 + }); 115 + || () 116 + }); 117 + } 118 + 119 + html! { 120 + <div> 121 + if let Some(err) = &*error_message { 122 + <p class="text-red-500">{ format!("Error during login: {}", err) }</p> 123 + <p><Link<AppRoute> to={AppRoute::Login}>{ "Try logging in again" }</Link<AppRoute>></p> 124 + } else { 125 + <p>{ "Processing login, please wait..." }</p> 126 + } 127 + </div> 128 + } 129 + }
+11
rust_demo_app/app_demo/src/pages/home.rs
··· 1 + use yew::prelude::*; 2 + 3 + #[function_component(HomePage)] 4 + pub fn home_page() -> Html { 5 + html! { 6 + <div> 7 + <h1>{ "Welcome to the ATProto Counter!" }</h1> 8 + // Counter display and increment button will go here 9 + </div> 10 + } 11 + }
+105
rust_demo_app/app_demo/src/pages/login.rs
··· 1 + use gloo::utils::window; 2 + use wasm_bindgen_futures::spawn_local; 3 + use web_sys::HtmlInputElement; 4 + use yew::prelude::*; 5 + 6 + use crate::oauth_client::get_oauth_client; 7 + // use crate::AppRoute; // Removed unused import 8 + use atrium_oauth::{AuthorizeOptions, KnownScope, Scope}; 9 + 10 + #[function_component(LoginPage)] 11 + pub fn login_page() -> Html { 12 + let pds_host = use_state(|| "https://bsky.social".to_string()); 13 + let error_message = use_state(|| None::<String>); 14 + 15 + let on_pds_host_input = { 16 + let pds_host = pds_host.clone(); 17 + Callback::from(move |e: InputEvent| { 18 + let input: HtmlInputElement = e.target_unchecked_into(); 19 + pds_host.set(input.value()); 20 + }) 21 + }; 22 + 23 + let on_submit = { 24 + let pds_host = pds_host.clone(); 25 + let error_message = error_message.clone(); 26 + 27 + Callback::from(move |e: SubmitEvent| { 28 + e.prevent_default(); 29 + let pds_host_val = pds_host.trim(); 30 + if pds_host_val.is_empty() { 31 + error_message.set(Some("PDS Host cannot be empty".to_string())); 32 + return; 33 + } 34 + if !pds_host_val.starts_with("https://") && !pds_host_val.starts_with("http://") { 35 + error_message.set(Some( 36 + "PDS Host must start with http:// or https://".to_string(), 37 + )); 38 + return; 39 + } 40 + error_message.set(None); // Clear previous error 41 + 42 + let pds_host_val = pds_host_val.to_string(); 43 + let error_message_clone = error_message.clone(); 44 + 45 + spawn_local(async move { 46 + match get_oauth_client() 47 + .await 48 + .authorize( 49 + pds_host_val.clone(), // PDS host for authorization 50 + AuthorizeOptions { 51 + scopes: vec![ 52 + Scope::Known(KnownScope::Atproto), 53 + Scope::Known(KnownScope::TransitionGeneric), 54 + ], 55 + // The state parameter is generated and stored by the authorize method itself. 56 + // It will be associated with the PKCE code verifier and the PDS host. 57 + ..Default::default() 58 + }, 59 + ) 60 + .await 61 + { 62 + Ok(auth_url_with_state) => { 63 + // auth_url_with_state includes the `state` query parameter generated by the SDK. 64 + // The SDK's state_store will have stored the pkce_code_verifier and pds_host against this state. 65 + if let Err(e) = window().location().set_href(auth_url_with_state.as_str()) { 66 + log::error!("Failed to redirect: {:?}", e); 67 + error_message_clone.set(Some(format!("Failed to redirect: {:?}", e))); 68 + } 69 + // No need to navigate with yew_router here, browser redirect handles it. 70 + } 71 + Err(e) => { 72 + log::error!("Failed to get authorization URL: {:?}", e); 73 + error_message_clone.set(Some(format!("Login failed: {:?}", e))); 74 + } 75 + } 76 + }); 77 + }) 78 + }; 79 + 80 + html! { 81 + <div class="w-full max-w-md mx-auto mt-10"> 82 + <form onsubmit={on_submit} class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"> 83 + <h2 class="text-2xl font-bold mb-6 text-center">{ "Login to ATProto" }</h2> 84 + <div class="mb-4"> 85 + <label class="block text-gray-700 text-sm font-bold mb-2" for="pds_host"> 86 + { "PDS Host" } 87 + </label> 88 + <input type="text" id="pds_host" value={(*pds_host).clone()} 89 + oninput={on_pds_host_input} 90 + class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" 91 + placeholder="e.g., https://bsky.social" /> 92 + </div> 93 + if let Some(err) = &*error_message { 94 + <p class="text-red-500 text-xs italic mb-4">{ err }</p> 95 + } 96 + <div class="flex items-center justify-between"> 97 + <button type="submit" 98 + class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"> 99 + { "Login" } 100 + </button> 101 + </div> 102 + </form> 103 + </div> 104 + } 105 + }
+3
rust_demo_app/app_demo/src/pages/mod.rs
··· 1 + pub mod callback; 2 + pub mod home; 3 + pub mod login;