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