Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

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