mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

at schema-errors 1501 lines 45 kB view raw
1import {BskyAgent} from '@atproto/api' 2import {describe, expect, it, jest} from '@jest/globals' 3 4import {agentToSessionAccountOrThrow} from '../agent' 5import {Action, getInitialState, reducer, State} from '../reducer' 6 7jest.mock('jwt-decode', () => ({ 8 jwtDecode(_token: string) { 9 return {} 10 }, 11})) 12 13describe('session', () => { 14 it('can log in and out', () => { 15 let state = getInitialState([]) 16 expect(printState(state)).toMatchInlineSnapshot(` 17 { 18 "accounts": [], 19 "currentAgentState": { 20 "agent": { 21 "service": "https://public.api.bsky.app/", 22 }, 23 "did": undefined, 24 }, 25 "needsPersist": false, 26 } 27 `) 28 29 const agent = new BskyAgent({service: 'https://alice.com'}) 30 agent.session = { 31 did: 'alice-did', 32 handle: 'alice.test', 33 accessJwt: 'alice-access-jwt-1', 34 refreshJwt: 'alice-refresh-jwt-1', 35 } 36 state = run(state, [ 37 { 38 type: 'switched-to-account', 39 newAgent: agent, 40 newAccount: agentToSessionAccountOrThrow(agent), 41 }, 42 ]) 43 expect(state.currentAgentState.did).toBe('alice-did') 44 expect(state.accounts.length).toBe(1) 45 expect(state.accounts[0].did).toBe('alice-did') 46 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1') 47 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-1') 48 expect(printState(state)).toMatchInlineSnapshot(` 49 { 50 "accounts": [ 51 { 52 "accessJwt": "alice-access-jwt-1", 53 "did": "alice-did", 54 "email": undefined, 55 "emailAuthFactor": false, 56 "emailConfirmed": false, 57 "handle": "alice.test", 58 "pdsUrl": undefined, 59 "refreshJwt": "alice-refresh-jwt-1", 60 "service": "https://alice.com/", 61 "signupQueued": false, 62 "status": undefined, 63 }, 64 ], 65 "currentAgentState": { 66 "agent": { 67 "service": "https://alice.com/", 68 }, 69 "did": "alice-did", 70 }, 71 "needsPersist": true, 72 } 73 `) 74 75 state = run(state, [ 76 { 77 type: 'logged-out', 78 }, 79 ]) 80 // Should keep the account but clear out the tokens. 81 expect(state.currentAgentState.did).toBe(undefined) 82 expect(state.accounts.length).toBe(1) 83 expect(state.accounts[0].did).toBe('alice-did') 84 expect(state.accounts[0].accessJwt).toBe(undefined) 85 expect(state.accounts[0].refreshJwt).toBe(undefined) 86 expect(printState(state)).toMatchInlineSnapshot(` 87 { 88 "accounts": [ 89 { 90 "accessJwt": undefined, 91 "did": "alice-did", 92 "email": undefined, 93 "emailAuthFactor": false, 94 "emailConfirmed": false, 95 "handle": "alice.test", 96 "pdsUrl": undefined, 97 "refreshJwt": undefined, 98 "service": "https://alice.com/", 99 "signupQueued": false, 100 "status": undefined, 101 }, 102 ], 103 "currentAgentState": { 104 "agent": { 105 "service": "https://public.api.bsky.app/", 106 }, 107 "did": undefined, 108 }, 109 "needsPersist": true, 110 } 111 `) 112 }) 113 114 it('switches to the latest account, stores all of them', () => { 115 let state = getInitialState([]) 116 117 const agent1 = new BskyAgent({service: 'https://alice.com'}) 118 agent1.session = { 119 did: 'alice-did', 120 handle: 'alice.test', 121 accessJwt: 'alice-access-jwt-1', 122 refreshJwt: 'alice-refresh-jwt-1', 123 } 124 state = run(state, [ 125 { 126 // Switch to Alice. 127 type: 'switched-to-account', 128 newAgent: agent1, 129 newAccount: agentToSessionAccountOrThrow(agent1), 130 }, 131 ]) 132 expect(state.accounts.length).toBe(1) 133 expect(state.accounts[0].did).toBe('alice-did') 134 expect(state.currentAgentState.did).toBe('alice-did') 135 expect(state.currentAgentState.agent).toBe(agent1) 136 expect(printState(state)).toMatchInlineSnapshot(` 137 { 138 "accounts": [ 139 { 140 "accessJwt": "alice-access-jwt-1", 141 "did": "alice-did", 142 "email": undefined, 143 "emailAuthFactor": false, 144 "emailConfirmed": false, 145 "handle": "alice.test", 146 "pdsUrl": undefined, 147 "refreshJwt": "alice-refresh-jwt-1", 148 "service": "https://alice.com/", 149 "signupQueued": false, 150 "status": undefined, 151 }, 152 ], 153 "currentAgentState": { 154 "agent": { 155 "service": "https://alice.com/", 156 }, 157 "did": "alice-did", 158 }, 159 "needsPersist": true, 160 } 161 `) 162 163 const agent2 = new BskyAgent({service: 'https://bob.com'}) 164 agent2.session = { 165 did: 'bob-did', 166 handle: 'bob.test', 167 accessJwt: 'bob-access-jwt-1', 168 refreshJwt: 'bob-refresh-jwt-1', 169 } 170 state = run(state, [ 171 { 172 // Switch to Bob. 173 type: 'switched-to-account', 174 newAgent: agent2, 175 newAccount: agentToSessionAccountOrThrow(agent2), 176 }, 177 ]) 178 expect(state.accounts.length).toBe(2) 179 // Bob should float upwards. 180 expect(state.accounts[0].did).toBe('bob-did') 181 expect(state.accounts[1].did).toBe('alice-did') 182 expect(state.currentAgentState.did).toBe('bob-did') 183 expect(state.currentAgentState.agent).toBe(agent2) 184 expect(printState(state)).toMatchInlineSnapshot(` 185 { 186 "accounts": [ 187 { 188 "accessJwt": "bob-access-jwt-1", 189 "did": "bob-did", 190 "email": undefined, 191 "emailAuthFactor": false, 192 "emailConfirmed": false, 193 "handle": "bob.test", 194 "pdsUrl": undefined, 195 "refreshJwt": "bob-refresh-jwt-1", 196 "service": "https://bob.com/", 197 "signupQueued": false, 198 "status": undefined, 199 }, 200 { 201 "accessJwt": "alice-access-jwt-1", 202 "did": "alice-did", 203 "email": undefined, 204 "emailAuthFactor": false, 205 "emailConfirmed": false, 206 "handle": "alice.test", 207 "pdsUrl": undefined, 208 "refreshJwt": "alice-refresh-jwt-1", 209 "service": "https://alice.com/", 210 "signupQueued": false, 211 "status": undefined, 212 }, 213 ], 214 "currentAgentState": { 215 "agent": { 216 "service": "https://bob.com/", 217 }, 218 "did": "bob-did", 219 }, 220 "needsPersist": true, 221 } 222 `) 223 224 const agent3 = new BskyAgent({service: 'https://alice.com'}) 225 agent3.session = { 226 did: 'alice-did', 227 handle: 'alice-updated.test', 228 accessJwt: 'alice-access-jwt-2', 229 refreshJwt: 'alice-refresh-jwt-2', 230 } 231 state = run(state, [ 232 { 233 // Switch back to Alice. 234 type: 'switched-to-account', 235 newAgent: agent3, 236 newAccount: agentToSessionAccountOrThrow(agent3), 237 }, 238 ]) 239 expect(state.accounts.length).toBe(2) 240 // Alice should float upwards. 241 expect(state.accounts[0].did).toBe('alice-did') 242 expect(state.accounts[0].handle).toBe('alice-updated.test') 243 expect(state.currentAgentState.did).toBe('alice-did') 244 expect(state.currentAgentState.agent).toBe(agent3) 245 expect(printState(state)).toMatchInlineSnapshot(` 246 { 247 "accounts": [ 248 { 249 "accessJwt": "alice-access-jwt-2", 250 "did": "alice-did", 251 "email": undefined, 252 "emailAuthFactor": false, 253 "emailConfirmed": false, 254 "handle": "alice-updated.test", 255 "pdsUrl": undefined, 256 "refreshJwt": "alice-refresh-jwt-2", 257 "service": "https://alice.com/", 258 "signupQueued": false, 259 "status": undefined, 260 }, 261 { 262 "accessJwt": "bob-access-jwt-1", 263 "did": "bob-did", 264 "email": undefined, 265 "emailAuthFactor": false, 266 "emailConfirmed": false, 267 "handle": "bob.test", 268 "pdsUrl": undefined, 269 "refreshJwt": "bob-refresh-jwt-1", 270 "service": "https://bob.com/", 271 "signupQueued": false, 272 "status": undefined, 273 }, 274 ], 275 "currentAgentState": { 276 "agent": { 277 "service": "https://alice.com/", 278 }, 279 "did": "alice-did", 280 }, 281 "needsPersist": true, 282 } 283 `) 284 285 const agent4 = new BskyAgent({service: 'https://jay.com'}) 286 agent4.session = { 287 did: 'jay-did', 288 handle: 'jay.test', 289 accessJwt: 'jay-access-jwt-1', 290 refreshJwt: 'jay-refresh-jwt-1', 291 } 292 state = run(state, [ 293 { 294 // Switch to Jay. 295 type: 'switched-to-account', 296 newAgent: agent4, 297 newAccount: agentToSessionAccountOrThrow(agent4), 298 }, 299 ]) 300 expect(state.accounts.length).toBe(3) 301 expect(state.accounts[0].did).toBe('jay-did') 302 expect(state.currentAgentState.did).toBe('jay-did') 303 expect(state.currentAgentState.agent).toBe(agent4) 304 expect(printState(state)).toMatchInlineSnapshot(` 305 { 306 "accounts": [ 307 { 308 "accessJwt": "jay-access-jwt-1", 309 "did": "jay-did", 310 "email": undefined, 311 "emailAuthFactor": false, 312 "emailConfirmed": false, 313 "handle": "jay.test", 314 "pdsUrl": undefined, 315 "refreshJwt": "jay-refresh-jwt-1", 316 "service": "https://jay.com/", 317 "signupQueued": false, 318 "status": undefined, 319 }, 320 { 321 "accessJwt": "alice-access-jwt-2", 322 "did": "alice-did", 323 "email": undefined, 324 "emailAuthFactor": false, 325 "emailConfirmed": false, 326 "handle": "alice-updated.test", 327 "pdsUrl": undefined, 328 "refreshJwt": "alice-refresh-jwt-2", 329 "service": "https://alice.com/", 330 "signupQueued": false, 331 "status": undefined, 332 }, 333 { 334 "accessJwt": "bob-access-jwt-1", 335 "did": "bob-did", 336 "email": undefined, 337 "emailAuthFactor": false, 338 "emailConfirmed": false, 339 "handle": "bob.test", 340 "pdsUrl": undefined, 341 "refreshJwt": "bob-refresh-jwt-1", 342 "service": "https://bob.com/", 343 "signupQueued": false, 344 "status": undefined, 345 }, 346 ], 347 "currentAgentState": { 348 "agent": { 349 "service": "https://jay.com/", 350 }, 351 "did": "jay-did", 352 }, 353 "needsPersist": true, 354 } 355 `) 356 357 state = run(state, [ 358 { 359 // Log everyone out. 360 type: 'logged-out', 361 }, 362 ]) 363 expect(state.accounts.length).toBe(3) 364 expect(state.currentAgentState.did).toBe(undefined) 365 // All tokens should be gone. 366 expect(state.accounts[0].accessJwt).toBe(undefined) 367 expect(state.accounts[0].refreshJwt).toBe(undefined) 368 expect(state.accounts[1].accessJwt).toBe(undefined) 369 expect(state.accounts[1].refreshJwt).toBe(undefined) 370 expect(state.accounts[2].accessJwt).toBe(undefined) 371 expect(state.accounts[2].refreshJwt).toBe(undefined) 372 expect(printState(state)).toMatchInlineSnapshot(` 373 { 374 "accounts": [ 375 { 376 "accessJwt": undefined, 377 "did": "jay-did", 378 "email": undefined, 379 "emailAuthFactor": false, 380 "emailConfirmed": false, 381 "handle": "jay.test", 382 "pdsUrl": undefined, 383 "refreshJwt": undefined, 384 "service": "https://jay.com/", 385 "signupQueued": false, 386 "status": undefined, 387 }, 388 { 389 "accessJwt": undefined, 390 "did": "alice-did", 391 "email": undefined, 392 "emailAuthFactor": false, 393 "emailConfirmed": false, 394 "handle": "alice-updated.test", 395 "pdsUrl": undefined, 396 "refreshJwt": undefined, 397 "service": "https://alice.com/", 398 "signupQueued": false, 399 "status": undefined, 400 }, 401 { 402 "accessJwt": undefined, 403 "did": "bob-did", 404 "email": undefined, 405 "emailAuthFactor": false, 406 "emailConfirmed": false, 407 "handle": "bob.test", 408 "pdsUrl": undefined, 409 "refreshJwt": undefined, 410 "service": "https://bob.com/", 411 "signupQueued": false, 412 "status": undefined, 413 }, 414 ], 415 "currentAgentState": { 416 "agent": { 417 "service": "https://public.api.bsky.app/", 418 }, 419 "did": undefined, 420 }, 421 "needsPersist": true, 422 } 423 `) 424 }) 425 426 it('can log back in after logging out', () => { 427 let state = getInitialState([]) 428 429 const agent1 = new BskyAgent({service: 'https://alice.com'}) 430 agent1.session = { 431 did: 'alice-did', 432 handle: 'alice.test', 433 accessJwt: 'alice-access-jwt-1', 434 refreshJwt: 'alice-refresh-jwt-1', 435 } 436 state = run(state, [ 437 { 438 type: 'switched-to-account', 439 newAgent: agent1, 440 newAccount: agentToSessionAccountOrThrow(agent1), 441 }, 442 ]) 443 expect(state.accounts.length).toBe(1) 444 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1') 445 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-1') 446 expect(state.currentAgentState.did).toBe('alice-did') 447 448 state = run(state, [ 449 { 450 type: 'logged-out', 451 }, 452 ]) 453 expect(state.accounts.length).toBe(1) 454 expect(state.accounts[0].accessJwt).toBe(undefined) 455 expect(state.accounts[0].refreshJwt).toBe(undefined) 456 expect(state.currentAgentState.did).toBe(undefined) 457 expect(printState(state)).toMatchInlineSnapshot(` 458 { 459 "accounts": [ 460 { 461 "accessJwt": undefined, 462 "did": "alice-did", 463 "email": undefined, 464 "emailAuthFactor": false, 465 "emailConfirmed": false, 466 "handle": "alice.test", 467 "pdsUrl": undefined, 468 "refreshJwt": undefined, 469 "service": "https://alice.com/", 470 "signupQueued": false, 471 "status": undefined, 472 }, 473 ], 474 "currentAgentState": { 475 "agent": { 476 "service": "https://public.api.bsky.app/", 477 }, 478 "did": undefined, 479 }, 480 "needsPersist": true, 481 } 482 `) 483 484 const agent2 = new BskyAgent({service: 'https://alice.com'}) 485 agent2.session = { 486 did: 'alice-did', 487 handle: 'alice.test', 488 accessJwt: 'alice-access-jwt-2', 489 refreshJwt: 'alice-refresh-jwt-2', 490 } 491 state = run(state, [ 492 { 493 type: 'switched-to-account', 494 newAgent: agent2, 495 newAccount: agentToSessionAccountOrThrow(agent2), 496 }, 497 ]) 498 expect(state.accounts.length).toBe(1) 499 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-2') 500 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-2') 501 expect(state.currentAgentState.did).toBe('alice-did') 502 expect(printState(state)).toMatchInlineSnapshot(` 503 { 504 "accounts": [ 505 { 506 "accessJwt": "alice-access-jwt-2", 507 "did": "alice-did", 508 "email": undefined, 509 "emailAuthFactor": false, 510 "emailConfirmed": false, 511 "handle": "alice.test", 512 "pdsUrl": undefined, 513 "refreshJwt": "alice-refresh-jwt-2", 514 "service": "https://alice.com/", 515 "signupQueued": false, 516 "status": undefined, 517 }, 518 ], 519 "currentAgentState": { 520 "agent": { 521 "service": "https://alice.com/", 522 }, 523 "did": "alice-did", 524 }, 525 "needsPersist": true, 526 } 527 `) 528 }) 529 530 it('can remove active account', () => { 531 let state = getInitialState([]) 532 533 const agent1 = new BskyAgent({service: 'https://alice.com'}) 534 agent1.session = { 535 did: 'alice-did', 536 handle: 'alice.test', 537 accessJwt: 'alice-access-jwt-1', 538 refreshJwt: 'alice-refresh-jwt-1', 539 } 540 state = run(state, [ 541 { 542 type: 'switched-to-account', 543 newAgent: agent1, 544 newAccount: agentToSessionAccountOrThrow(agent1), 545 }, 546 ]) 547 expect(state.accounts.length).toBe(1) 548 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1') 549 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-1') 550 expect(state.currentAgentState.did).toBe('alice-did') 551 552 state = run(state, [ 553 { 554 type: 'removed-account', 555 accountDid: 'alice-did', 556 }, 557 ]) 558 expect(state.accounts.length).toBe(0) 559 expect(state.currentAgentState.did).toBe(undefined) 560 expect(printState(state)).toMatchInlineSnapshot(` 561 { 562 "accounts": [], 563 "currentAgentState": { 564 "agent": { 565 "service": "https://public.api.bsky.app/", 566 }, 567 "did": undefined, 568 }, 569 "needsPersist": true, 570 } 571 `) 572 }) 573 574 it('can remove inactive account', () => { 575 let state = getInitialState([]) 576 577 const agent1 = new BskyAgent({service: 'https://alice.com'}) 578 agent1.session = { 579 did: 'alice-did', 580 handle: 'alice.test', 581 accessJwt: 'alice-access-jwt-1', 582 refreshJwt: 'alice-refresh-jwt-1', 583 } 584 const agent2 = new BskyAgent({service: 'https://bob.com'}) 585 agent2.session = { 586 did: 'bob-did', 587 handle: 'bob.test', 588 accessJwt: 'bob-access-jwt-1', 589 refreshJwt: 'bob-refresh-jwt-1', 590 } 591 state = run(state, [ 592 { 593 type: 'switched-to-account', 594 newAgent: agent1, 595 newAccount: agentToSessionAccountOrThrow(agent1), 596 }, 597 { 598 type: 'switched-to-account', 599 newAgent: agent2, 600 newAccount: agentToSessionAccountOrThrow(agent2), 601 }, 602 ]) 603 expect(state.accounts.length).toBe(2) 604 expect(state.currentAgentState.did).toBe('bob-did') 605 606 state = run(state, [ 607 { 608 type: 'removed-account', 609 accountDid: 'alice-did', 610 }, 611 ]) 612 expect(state.accounts.length).toBe(1) 613 expect(state.currentAgentState.did).toBe('bob-did') 614 expect(printState(state)).toMatchInlineSnapshot(` 615 { 616 "accounts": [ 617 { 618 "accessJwt": "bob-access-jwt-1", 619 "did": "bob-did", 620 "email": undefined, 621 "emailAuthFactor": false, 622 "emailConfirmed": false, 623 "handle": "bob.test", 624 "pdsUrl": undefined, 625 "refreshJwt": "bob-refresh-jwt-1", 626 "service": "https://bob.com/", 627 "signupQueued": false, 628 "status": undefined, 629 }, 630 ], 631 "currentAgentState": { 632 "agent": { 633 "service": "https://bob.com/", 634 }, 635 "did": "bob-did", 636 }, 637 "needsPersist": true, 638 } 639 `) 640 641 state = run(state, [ 642 { 643 type: 'removed-account', 644 accountDid: 'bob-did', 645 }, 646 ]) 647 expect(state.accounts.length).toBe(0) 648 expect(state.currentAgentState.did).toBe(undefined) 649 }) 650 651 it('updates stored account with refreshed tokens', () => { 652 let state = getInitialState([]) 653 654 const agent1 = new BskyAgent({service: 'https://alice.com'}) 655 agent1.session = { 656 did: 'alice-did', 657 handle: 'alice.test', 658 accessJwt: 'alice-access-jwt-1', 659 refreshJwt: 'alice-refresh-jwt-1', 660 } 661 state = run(state, [ 662 { 663 type: 'switched-to-account', 664 newAgent: agent1, 665 newAccount: agentToSessionAccountOrThrow(agent1), 666 }, 667 ]) 668 expect(state.accounts.length).toBe(1) 669 expect(state.currentAgentState.did).toBe('alice-did') 670 671 agent1.session = { 672 did: 'alice-did', 673 handle: 'alice-updated.test', 674 accessJwt: 'alice-access-jwt-2', 675 refreshJwt: 'alice-refresh-jwt-2', 676 email: 'alice@foo.bar', 677 emailAuthFactor: false, 678 emailConfirmed: false, 679 } 680 state = run(state, [ 681 { 682 type: 'received-agent-event', 683 accountDid: 'alice-did', 684 agent: agent1, 685 refreshedAccount: agentToSessionAccountOrThrow(agent1), 686 sessionEvent: 'update', 687 }, 688 ]) 689 expect(state.accounts.length).toBe(1) 690 expect(state.accounts[0].email).toBe('alice@foo.bar') 691 expect(state.accounts[0].handle).toBe('alice-updated.test') 692 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-2') 693 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-2') 694 expect(state.currentAgentState.did).toBe('alice-did') 695 expect(printState(state)).toMatchInlineSnapshot(` 696 { 697 "accounts": [ 698 { 699 "accessJwt": "alice-access-jwt-2", 700 "did": "alice-did", 701 "email": "alice@foo.bar", 702 "emailAuthFactor": false, 703 "emailConfirmed": false, 704 "handle": "alice-updated.test", 705 "pdsUrl": undefined, 706 "refreshJwt": "alice-refresh-jwt-2", 707 "service": "https://alice.com/", 708 "signupQueued": false, 709 "status": undefined, 710 }, 711 ], 712 "currentAgentState": { 713 "agent": { 714 "service": "https://alice.com/", 715 }, 716 "did": "alice-did", 717 }, 718 "needsPersist": true, 719 } 720 `) 721 722 agent1.session = { 723 did: 'alice-did', 724 handle: 'alice-updated.test', 725 accessJwt: 'alice-access-jwt-3', 726 refreshJwt: 'alice-refresh-jwt-3', 727 email: 'alice@foo.baz', 728 emailAuthFactor: true, 729 emailConfirmed: true, 730 } 731 state = run(state, [ 732 { 733 type: 'received-agent-event', 734 accountDid: 'alice-did', 735 agent: agent1, 736 refreshedAccount: agentToSessionAccountOrThrow(agent1), 737 sessionEvent: 'update', 738 }, 739 ]) 740 expect(state.accounts.length).toBe(1) 741 expect(state.accounts[0].email).toBe('alice@foo.baz') 742 expect(state.accounts[0].handle).toBe('alice-updated.test') 743 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-3') 744 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-3') 745 expect(state.currentAgentState.did).toBe('alice-did') 746 expect(printState(state)).toMatchInlineSnapshot(` 747 { 748 "accounts": [ 749 { 750 "accessJwt": "alice-access-jwt-3", 751 "did": "alice-did", 752 "email": "alice@foo.baz", 753 "emailAuthFactor": true, 754 "emailConfirmed": true, 755 "handle": "alice-updated.test", 756 "pdsUrl": undefined, 757 "refreshJwt": "alice-refresh-jwt-3", 758 "service": "https://alice.com/", 759 "signupQueued": false, 760 "status": undefined, 761 }, 762 ], 763 "currentAgentState": { 764 "agent": { 765 "service": "https://alice.com/", 766 }, 767 "did": "alice-did", 768 }, 769 "needsPersist": true, 770 } 771 `) 772 773 agent1.session = { 774 did: 'alice-did', 775 handle: 'alice-updated.test', 776 accessJwt: 'alice-access-jwt-4', 777 refreshJwt: 'alice-refresh-jwt-4', 778 email: 'alice@foo.baz', 779 emailAuthFactor: false, 780 emailConfirmed: false, 781 } 782 state = run(state, [ 783 { 784 type: 'received-agent-event', 785 accountDid: 'alice-did', 786 agent: agent1, 787 refreshedAccount: agentToSessionAccountOrThrow(agent1), 788 sessionEvent: 'update', 789 }, 790 ]) 791 expect(state.accounts.length).toBe(1) 792 expect(state.accounts[0].email).toBe('alice@foo.baz') 793 expect(state.accounts[0].handle).toBe('alice-updated.test') 794 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-4') 795 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-4') 796 expect(state.currentAgentState.did).toBe('alice-did') 797 expect(printState(state)).toMatchInlineSnapshot(` 798 { 799 "accounts": [ 800 { 801 "accessJwt": "alice-access-jwt-4", 802 "did": "alice-did", 803 "email": "alice@foo.baz", 804 "emailAuthFactor": false, 805 "emailConfirmed": false, 806 "handle": "alice-updated.test", 807 "pdsUrl": undefined, 808 "refreshJwt": "alice-refresh-jwt-4", 809 "service": "https://alice.com/", 810 "signupQueued": false, 811 "status": undefined, 812 }, 813 ], 814 "currentAgentState": { 815 "agent": { 816 "service": "https://alice.com/", 817 }, 818 "did": "alice-did", 819 }, 820 "needsPersist": true, 821 } 822 `) 823 }) 824 825 it('bails out of update on identical objects', () => { 826 let state = getInitialState([]) 827 828 const agent1 = new BskyAgent({service: 'https://alice.com'}) 829 agent1.session = { 830 did: 'alice-did', 831 handle: 'alice.test', 832 accessJwt: 'alice-access-jwt-1', 833 refreshJwt: 'alice-refresh-jwt-1', 834 } 835 state = run(state, [ 836 { 837 type: 'switched-to-account', 838 newAgent: agent1, 839 newAccount: agentToSessionAccountOrThrow(agent1), 840 }, 841 ]) 842 expect(state.accounts.length).toBe(1) 843 expect(state.currentAgentState.did).toBe('alice-did') 844 845 agent1.session = { 846 did: 'alice-did', 847 handle: 'alice-updated.test', 848 accessJwt: 'alice-access-jwt-2', 849 refreshJwt: 'alice-refresh-jwt-2', 850 } 851 state = run(state, [ 852 { 853 type: 'received-agent-event', 854 accountDid: 'alice-did', 855 agent: agent1, 856 refreshedAccount: agentToSessionAccountOrThrow(agent1), 857 sessionEvent: 'update', 858 }, 859 ]) 860 expect(state.accounts.length).toBe(1) 861 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-2') 862 863 const lastState = state 864 state = run(state, [ 865 { 866 type: 'received-agent-event', 867 accountDid: 'alice-did', 868 agent: agent1, 869 refreshedAccount: agentToSessionAccountOrThrow(agent1), 870 sessionEvent: 'update', 871 }, 872 ]) 873 expect(lastState === state).toBe(true) 874 875 agent1.session = { 876 did: 'alice-did', 877 handle: 'alice-updated.test', 878 accessJwt: 'alice-access-jwt-3', 879 refreshJwt: 'alice-refresh-jwt-3', 880 } 881 state = run(state, [ 882 { 883 type: 'received-agent-event', 884 accountDid: 'alice-did', 885 agent: agent1, 886 refreshedAccount: agentToSessionAccountOrThrow(agent1), 887 sessionEvent: 'update', 888 }, 889 ]) 890 expect(state.accounts.length).toBe(1) 891 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-3') 892 }) 893 894 it('accepts updates from a stale agent', () => { 895 let state = getInitialState([]) 896 897 const agent1 = new BskyAgent({service: 'https://alice.com'}) 898 agent1.session = { 899 did: 'alice-did', 900 handle: 'alice.test', 901 accessJwt: 'alice-access-jwt-1', 902 refreshJwt: 'alice-refresh-jwt-1', 903 } 904 905 const agent2 = new BskyAgent({service: 'https://bob.com'}) 906 agent2.session = { 907 did: 'bob-did', 908 handle: 'bob.test', 909 accessJwt: 'bob-access-jwt-1', 910 refreshJwt: 'bob-refresh-jwt-1', 911 } 912 913 state = run(state, [ 914 { 915 // Switch to Alice. 916 type: 'switched-to-account', 917 newAgent: agent1, 918 newAccount: agentToSessionAccountOrThrow(agent1), 919 }, 920 { 921 // Switch to Bob. 922 type: 'switched-to-account', 923 newAgent: agent2, 924 newAccount: agentToSessionAccountOrThrow(agent2), 925 }, 926 ]) 927 expect(state.accounts.length).toBe(2) 928 expect(state.currentAgentState.did).toBe('bob-did') 929 930 agent1.session = { 931 did: 'alice-did', 932 handle: 'alice-updated.test', 933 accessJwt: 'alice-access-jwt-2', 934 refreshJwt: 'alice-refresh-jwt-2', 935 email: 'alice@foo.bar', 936 emailAuthFactor: false, 937 emailConfirmed: false, 938 } 939 state = run(state, [ 940 { 941 type: 'received-agent-event', 942 accountDid: 'alice-did', 943 agent: agent1, 944 refreshedAccount: agentToSessionAccountOrThrow(agent1), 945 sessionEvent: 'update', 946 }, 947 ]) 948 expect(state.accounts.length).toBe(2) 949 expect(state.accounts[1].did).toBe('alice-did') 950 // Should update Alice's tokens because otherwise they'll be stale. 951 expect(state.accounts[1].handle).toBe('alice-updated.test') 952 expect(state.accounts[1].accessJwt).toBe('alice-access-jwt-2') 953 expect(state.accounts[1].refreshJwt).toBe('alice-refresh-jwt-2') 954 expect(printState(state)).toMatchInlineSnapshot(` 955 { 956 "accounts": [ 957 { 958 "accessJwt": "bob-access-jwt-1", 959 "did": "bob-did", 960 "email": undefined, 961 "emailAuthFactor": false, 962 "emailConfirmed": false, 963 "handle": "bob.test", 964 "pdsUrl": undefined, 965 "refreshJwt": "bob-refresh-jwt-1", 966 "service": "https://bob.com/", 967 "signupQueued": false, 968 "status": undefined, 969 }, 970 { 971 "accessJwt": "alice-access-jwt-2", 972 "did": "alice-did", 973 "email": "alice@foo.bar", 974 "emailAuthFactor": false, 975 "emailConfirmed": false, 976 "handle": "alice-updated.test", 977 "pdsUrl": undefined, 978 "refreshJwt": "alice-refresh-jwt-2", 979 "service": "https://alice.com/", 980 "signupQueued": false, 981 "status": undefined, 982 }, 983 ], 984 "currentAgentState": { 985 "agent": { 986 "service": "https://bob.com/", 987 }, 988 "did": "bob-did", 989 }, 990 "needsPersist": true, 991 } 992 `) 993 994 agent2.session = { 995 did: 'bob-did', 996 handle: 'bob-updated.test', 997 accessJwt: 'bob-access-jwt-2', 998 refreshJwt: 'bob-refresh-jwt-2', 999 } 1000 state = run(state, [ 1001 { 1002 // Update Bob. 1003 type: 'received-agent-event', 1004 accountDid: 'bob-did', 1005 agent: agent2, 1006 refreshedAccount: agentToSessionAccountOrThrow(agent2), 1007 sessionEvent: 'update', 1008 }, 1009 ]) 1010 expect(state.accounts.length).toBe(2) 1011 expect(state.accounts[0].did).toBe('bob-did') 1012 // Should update Bob's tokens because otherwise they'll be stale. 1013 expect(state.accounts[0].handle).toBe('bob-updated.test') 1014 expect(state.accounts[0].accessJwt).toBe('bob-access-jwt-2') 1015 expect(state.accounts[0].refreshJwt).toBe('bob-refresh-jwt-2') 1016 expect(printState(state)).toMatchInlineSnapshot(` 1017 { 1018 "accounts": [ 1019 { 1020 "accessJwt": "bob-access-jwt-2", 1021 "did": "bob-did", 1022 "email": undefined, 1023 "emailAuthFactor": false, 1024 "emailConfirmed": false, 1025 "handle": "bob-updated.test", 1026 "pdsUrl": undefined, 1027 "refreshJwt": "bob-refresh-jwt-2", 1028 "service": "https://bob.com/", 1029 "signupQueued": false, 1030 "status": undefined, 1031 }, 1032 { 1033 "accessJwt": "alice-access-jwt-2", 1034 "did": "alice-did", 1035 "email": "alice@foo.bar", 1036 "emailAuthFactor": false, 1037 "emailConfirmed": false, 1038 "handle": "alice-updated.test", 1039 "pdsUrl": undefined, 1040 "refreshJwt": "alice-refresh-jwt-2", 1041 "service": "https://alice.com/", 1042 "signupQueued": false, 1043 "status": undefined, 1044 }, 1045 ], 1046 "currentAgentState": { 1047 "agent": { 1048 "service": "https://bob.com/", 1049 }, 1050 "did": "bob-did", 1051 }, 1052 "needsPersist": true, 1053 } 1054 `) 1055 1056 // Ignore other events for inactive agent. 1057 const lastState = state 1058 agent1.session = undefined 1059 state = run(state, [ 1060 { 1061 type: 'received-agent-event', 1062 accountDid: 'alice-did', 1063 agent: agent1, 1064 refreshedAccount: undefined, 1065 sessionEvent: 'network-error', 1066 }, 1067 ]) 1068 expect(lastState === state).toBe(true) 1069 state = run(state, [ 1070 { 1071 type: 'received-agent-event', 1072 accountDid: 'alice-did', 1073 agent: agent1, 1074 refreshedAccount: undefined, 1075 sessionEvent: 'expired', 1076 }, 1077 ]) 1078 expect(lastState === state).toBe(true) 1079 }) 1080 1081 it('ignores updates from a removed agent', () => { 1082 let state = getInitialState([]) 1083 1084 const agent1 = new BskyAgent({service: 'https://alice.com'}) 1085 agent1.session = { 1086 did: 'alice-did', 1087 handle: 'alice.test', 1088 accessJwt: 'alice-access-jwt-1', 1089 refreshJwt: 'alice-refresh-jwt-1', 1090 } 1091 1092 const agent2 = new BskyAgent({service: 'https://bob.com'}) 1093 agent2.session = { 1094 did: 'bob-did', 1095 handle: 'bob.test', 1096 accessJwt: 'bob-access-jwt-1', 1097 refreshJwt: 'bob-refresh-jwt-1', 1098 } 1099 1100 state = run(state, [ 1101 { 1102 type: 'switched-to-account', 1103 newAgent: agent1, 1104 newAccount: agentToSessionAccountOrThrow(agent1), 1105 }, 1106 { 1107 type: 'switched-to-account', 1108 newAgent: agent2, 1109 newAccount: agentToSessionAccountOrThrow(agent2), 1110 }, 1111 { 1112 type: 'removed-account', 1113 accountDid: 'alice-did', 1114 }, 1115 ]) 1116 expect(state.accounts.length).toBe(1) 1117 expect(state.currentAgentState.did).toBe('bob-did') 1118 1119 agent1.session = { 1120 did: 'alice-did', 1121 handle: 'alice.test', 1122 accessJwt: 'alice-access-jwt-2', 1123 refreshJwt: 'alice-refresh-jwt-2', 1124 } 1125 state = run(state, [ 1126 { 1127 type: 'received-agent-event', 1128 accountDid: 'alice-did', 1129 agent: agent1, 1130 refreshedAccount: agentToSessionAccountOrThrow(agent1), 1131 sessionEvent: 'update', 1132 }, 1133 ]) 1134 expect(state.accounts.length).toBe(1) 1135 expect(state.accounts[0].did).toBe('bob-did') 1136 expect(state.accounts[0].accessJwt).toBe('bob-access-jwt-1') 1137 expect(state.currentAgentState.did).toBe('bob-did') 1138 }) 1139 1140 it('does soft logout on network error', () => { 1141 let state = getInitialState([]) 1142 1143 const agent1 = new BskyAgent({service: 'https://alice.com'}) 1144 agent1.session = { 1145 did: 'alice-did', 1146 handle: 'alice.test', 1147 accessJwt: 'alice-access-jwt-1', 1148 refreshJwt: 'alice-refresh-jwt-1', 1149 } 1150 state = run(state, [ 1151 { 1152 // Switch to Alice. 1153 type: 'switched-to-account', 1154 newAgent: agent1, 1155 newAccount: agentToSessionAccountOrThrow(agent1), 1156 }, 1157 ]) 1158 expect(state.accounts.length).toBe(1) 1159 expect(state.currentAgentState.did).toBe('alice-did') 1160 1161 agent1.session = undefined 1162 state = run(state, [ 1163 { 1164 type: 'received-agent-event', 1165 accountDid: 'alice-did', 1166 agent: agent1, 1167 refreshedAccount: undefined, 1168 sessionEvent: 'network-error', 1169 }, 1170 ]) 1171 expect(state.accounts.length).toBe(1) 1172 // Network error should reset current user but not reset the tokens. 1173 // TODO: We might want to remove or change this behavior? 1174 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1') 1175 expect(state.accounts[0].refreshJwt).toBe('alice-refresh-jwt-1') 1176 expect(state.currentAgentState.did).toBe(undefined) 1177 expect(printState(state)).toMatchInlineSnapshot(` 1178 { 1179 "accounts": [ 1180 { 1181 "accessJwt": "alice-access-jwt-1", 1182 "did": "alice-did", 1183 "email": undefined, 1184 "emailAuthFactor": false, 1185 "emailConfirmed": false, 1186 "handle": "alice.test", 1187 "pdsUrl": undefined, 1188 "refreshJwt": "alice-refresh-jwt-1", 1189 "service": "https://alice.com/", 1190 "signupQueued": false, 1191 "status": undefined, 1192 }, 1193 ], 1194 "currentAgentState": { 1195 "agent": { 1196 "service": "https://public.api.bsky.app/", 1197 }, 1198 "did": undefined, 1199 }, 1200 "needsPersist": true, 1201 } 1202 `) 1203 }) 1204 1205 it('resets tokens on expired event', () => { 1206 let state = getInitialState([]) 1207 1208 const agent1 = new BskyAgent({service: 'https://alice.com'}) 1209 agent1.session = { 1210 did: 'alice-did', 1211 handle: 'alice.test', 1212 accessJwt: 'alice-access-jwt-1', 1213 refreshJwt: 'alice-refresh-jwt-1', 1214 } 1215 state = run(state, [ 1216 { 1217 type: 'switched-to-account', 1218 newAgent: agent1, 1219 newAccount: agentToSessionAccountOrThrow(agent1), 1220 }, 1221 ]) 1222 expect(state.accounts.length).toBe(1) 1223 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1') 1224 expect(state.currentAgentState.did).toBe('alice-did') 1225 1226 agent1.session = undefined 1227 state = run(state, [ 1228 { 1229 type: 'received-agent-event', 1230 accountDid: 'alice-did', 1231 agent: agent1, 1232 refreshedAccount: undefined, 1233 sessionEvent: 'expired', 1234 }, 1235 ]) 1236 expect(state.accounts.length).toBe(1) 1237 expect(state.accounts[0].accessJwt).toBe(undefined) 1238 expect(state.accounts[0].refreshJwt).toBe(undefined) 1239 expect(state.currentAgentState.did).toBe(undefined) 1240 expect(printState(state)).toMatchInlineSnapshot(` 1241 { 1242 "accounts": [ 1243 { 1244 "accessJwt": undefined, 1245 "did": "alice-did", 1246 "email": undefined, 1247 "emailAuthFactor": false, 1248 "emailConfirmed": false, 1249 "handle": "alice.test", 1250 "pdsUrl": undefined, 1251 "refreshJwt": undefined, 1252 "service": "https://alice.com/", 1253 "signupQueued": false, 1254 "status": undefined, 1255 }, 1256 ], 1257 "currentAgentState": { 1258 "agent": { 1259 "service": "https://public.api.bsky.app/", 1260 }, 1261 "did": undefined, 1262 }, 1263 "needsPersist": true, 1264 } 1265 `) 1266 }) 1267 1268 it('resets tokens on created-failed event', () => { 1269 let state = getInitialState([]) 1270 1271 const agent1 = new BskyAgent({service: 'https://alice.com'}) 1272 agent1.session = { 1273 did: 'alice-did', 1274 handle: 'alice.test', 1275 accessJwt: 'alice-access-jwt-1', 1276 refreshJwt: 'alice-refresh-jwt-1', 1277 } 1278 state = run(state, [ 1279 { 1280 type: 'switched-to-account', 1281 newAgent: agent1, 1282 newAccount: agentToSessionAccountOrThrow(agent1), 1283 }, 1284 ]) 1285 expect(state.accounts.length).toBe(1) 1286 expect(state.accounts[0].accessJwt).toBe('alice-access-jwt-1') 1287 expect(state.currentAgentState.did).toBe('alice-did') 1288 1289 agent1.session = undefined 1290 state = run(state, [ 1291 { 1292 type: 'received-agent-event', 1293 accountDid: 'alice-did', 1294 agent: agent1, 1295 refreshedAccount: undefined, 1296 sessionEvent: 'create-failed', 1297 }, 1298 ]) 1299 expect(state.accounts.length).toBe(1) 1300 expect(state.accounts[0].accessJwt).toBe(undefined) 1301 expect(state.accounts[0].refreshJwt).toBe(undefined) 1302 expect(state.currentAgentState.did).toBe(undefined) 1303 expect(printState(state)).toMatchInlineSnapshot(` 1304 { 1305 "accounts": [ 1306 { 1307 "accessJwt": undefined, 1308 "did": "alice-did", 1309 "email": undefined, 1310 "emailAuthFactor": false, 1311 "emailConfirmed": false, 1312 "handle": "alice.test", 1313 "pdsUrl": undefined, 1314 "refreshJwt": undefined, 1315 "service": "https://alice.com/", 1316 "signupQueued": false, 1317 "status": undefined, 1318 }, 1319 ], 1320 "currentAgentState": { 1321 "agent": { 1322 "service": "https://public.api.bsky.app/", 1323 }, 1324 "did": undefined, 1325 }, 1326 "needsPersist": true, 1327 } 1328 `) 1329 }) 1330 1331 it('replaces local accounts with synced accounts', () => { 1332 let state = getInitialState([]) 1333 1334 const agent1 = new BskyAgent({service: 'https://alice.com'}) 1335 agent1.session = { 1336 did: 'alice-did', 1337 handle: 'alice.test', 1338 accessJwt: 'alice-access-jwt-1', 1339 refreshJwt: 'alice-refresh-jwt-1', 1340 } 1341 const agent2 = new BskyAgent({service: 'https://bob.com'}) 1342 agent2.session = { 1343 did: 'bob-did', 1344 handle: 'bob.test', 1345 accessJwt: 'bob-access-jwt-1', 1346 refreshJwt: 'bob-refresh-jwt-1', 1347 } 1348 state = run(state, [ 1349 { 1350 type: 'switched-to-account', 1351 newAgent: agent1, 1352 newAccount: agentToSessionAccountOrThrow(agent1), 1353 }, 1354 { 1355 type: 'switched-to-account', 1356 newAgent: agent2, 1357 newAccount: agentToSessionAccountOrThrow(agent2), 1358 }, 1359 ]) 1360 expect(state.accounts.length).toBe(2) 1361 expect(state.currentAgentState.did).toBe('bob-did') 1362 1363 const anotherTabAgent1 = new BskyAgent({service: 'https://jay.com'}) 1364 anotherTabAgent1.session = { 1365 did: 'jay-did', 1366 handle: 'jay.test', 1367 accessJwt: 'jay-access-jwt-1', 1368 refreshJwt: 'jay-refresh-jwt-1', 1369 } 1370 const anotherTabAgent2 = new BskyAgent({service: 'https://alice.com'}) 1371 anotherTabAgent2.session = { 1372 did: 'bob-did', 1373 handle: 'bob.test', 1374 accessJwt: 'bob-access-jwt-2', 1375 refreshJwt: 'bob-refresh-jwt-2', 1376 } 1377 state = run(state, [ 1378 { 1379 type: 'synced-accounts', 1380 syncedAccounts: [ 1381 agentToSessionAccountOrThrow(anotherTabAgent1), 1382 agentToSessionAccountOrThrow(anotherTabAgent2), 1383 ], 1384 syncedCurrentDid: 'bob-did', 1385 }, 1386 ]) 1387 expect(state.accounts.length).toBe(2) 1388 expect(state.accounts[0].did).toBe('jay-did') 1389 expect(state.accounts[1].did).toBe('bob-did') 1390 expect(state.accounts[1].accessJwt).toBe('bob-access-jwt-2') 1391 // Keep Bob logged in. 1392 // (We patch up agent.session outside the reducer for this to work.) 1393 expect(state.currentAgentState.did).toBe('bob-did') 1394 expect(state.needsPersist).toBe(false) 1395 expect(printState(state)).toMatchInlineSnapshot(` 1396 { 1397 "accounts": [ 1398 { 1399 "accessJwt": "jay-access-jwt-1", 1400 "did": "jay-did", 1401 "email": undefined, 1402 "emailAuthFactor": false, 1403 "emailConfirmed": false, 1404 "handle": "jay.test", 1405 "pdsUrl": undefined, 1406 "refreshJwt": "jay-refresh-jwt-1", 1407 "service": "https://jay.com/", 1408 "signupQueued": false, 1409 "status": undefined, 1410 }, 1411 { 1412 "accessJwt": "bob-access-jwt-2", 1413 "did": "bob-did", 1414 "email": undefined, 1415 "emailAuthFactor": false, 1416 "emailConfirmed": false, 1417 "handle": "bob.test", 1418 "pdsUrl": undefined, 1419 "refreshJwt": "bob-refresh-jwt-2", 1420 "service": "https://alice.com/", 1421 "signupQueued": false, 1422 "status": undefined, 1423 }, 1424 ], 1425 "currentAgentState": { 1426 "agent": { 1427 "service": "https://bob.com/", 1428 }, 1429 "did": "bob-did", 1430 }, 1431 "needsPersist": false, 1432 } 1433 `) 1434 1435 const anotherTabAgent3 = new BskyAgent({service: 'https://clarence.com'}) 1436 anotherTabAgent3.session = { 1437 did: 'clarence-did', 1438 handle: 'clarence.test', 1439 accessJwt: 'clarence-access-jwt-2', 1440 refreshJwt: 'clarence-refresh-jwt-2', 1441 } 1442 state = run(state, [ 1443 { 1444 type: 'synced-accounts', 1445 syncedAccounts: [agentToSessionAccountOrThrow(anotherTabAgent3)], 1446 syncedCurrentDid: 'clarence-did', 1447 }, 1448 ]) 1449 expect(state.accounts.length).toBe(1) 1450 expect(state.accounts[0].did).toBe('clarence-did') 1451 // Log out because we have no matching user. 1452 // (In practice, we'll resume this session outside the reducer.) 1453 expect(state.currentAgentState.did).toBe(undefined) 1454 expect(state.needsPersist).toBe(false) 1455 expect(printState(state)).toMatchInlineSnapshot(` 1456 { 1457 "accounts": [ 1458 { 1459 "accessJwt": "clarence-access-jwt-2", 1460 "did": "clarence-did", 1461 "email": undefined, 1462 "emailAuthFactor": false, 1463 "emailConfirmed": false, 1464 "handle": "clarence.test", 1465 "pdsUrl": undefined, 1466 "refreshJwt": "clarence-refresh-jwt-2", 1467 "service": "https://clarence.com/", 1468 "signupQueued": false, 1469 "status": undefined, 1470 }, 1471 ], 1472 "currentAgentState": { 1473 "agent": { 1474 "service": "https://public.api.bsky.app/", 1475 }, 1476 "did": undefined, 1477 }, 1478 "needsPersist": false, 1479 } 1480 `) 1481 }) 1482}) 1483 1484function run(initialState: State, actions: Action[]): State { 1485 let state = initialState 1486 for (let action of actions) { 1487 state = reducer(state, action) 1488 } 1489 return state 1490} 1491 1492function printState(state: State) { 1493 return { 1494 accounts: state.accounts, 1495 currentAgentState: { 1496 agent: {service: state.currentAgentState.agent.service}, 1497 did: state.currentAgentState.did, 1498 }, 1499 needsPersist: state.needsPersist, 1500 } 1501}