teams-cli 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {teams_cli-0.2.3 → teams_cli-0.2.4}/CHANGELOG.md +11 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/Cargo.lock +1 -1
- {teams_cli-0.2.3 → teams_cli-0.2.4}/Cargo.toml +1 -1
- {teams_cli-0.2.3 → teams_cli-0.2.4}/PKG-INFO +1 -1
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/browser.rs +4 -2
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/helper.rs +1 -1
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop.rs +37 -6
- teams_cli-0.2.4/src/desktop_cache.js +41 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop_people.js +1 -1
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/desktop_scripts.test.cjs +42 -2
- teams_cli-0.2.3/src/desktop_cache.js +0 -17
- {teams_cli-0.2.3 → teams_cli-0.2.4}/.github/workflows/ci.yml +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/.github/workflows/dependencies.yml +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/.github/workflows/release.yml +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/.gitignore +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/.mise.toml +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/LICENSE +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/Makefile +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/README.md +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/build.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/docs/releases.md +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/examples/validate_schema.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/pyproject.toml +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/scripts/smoke-release.py +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/auth/pass.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/auth/process.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/auth/wsl.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/auth/wsl_credentials.ps1 +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/auth.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/backend.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/cli.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/config.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/delivery.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/avatars.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/browser_children.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/browser_core.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/browser_install.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/browser_lifetime.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/browser_setup.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/cdp.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/connection.ps1 +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/connection.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/install.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/macos.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/owner.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/session.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop/windows.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop.js +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop_core.js +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/desktop_send.js +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/error.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/graph.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/lib.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/main.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/output.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/render.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/schema.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/setup/capabilities.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/setup.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/teams_cli_alias.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/src/tui.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/browser_lifetime.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/cli.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/connection.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/connection.test.ps1 +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/credentials.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/desktop.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/desktop_scripts.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/fixtures/clispec-v0.3.json +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/js/people.test.mjs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/package_alias.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/pass_credentials.sh +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/release_smoke.rs +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/tests/wsl_credentials.test.ps1 +0 -0
- {teams_cli-0.2.3 → teams_cli-0.2.4}/vership.toml +0 -0
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.4](https://github.com/rvben/teams-cli/compare/v0.2.3...v0.2.4) - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- When Teams keeps caches for several accounts, guest organizations or display languages, the desktop backend now reads the one for the signed-in user, tenant and display language instead of refusing.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Desktop errors now tell apart two cases: personal and work/school Teams both being open, which `--desktop-account` resolves, and one surface holding cached data for several accounts or organizations, which it does not.
|
|
19
|
+
- Windows desktop helpers use protocol 5. WSL requires the helper from the same CLI release.
|
|
20
|
+
|
|
10
21
|
## [0.2.3](https://github.com/rvben/teams-cli/compare/v0.2.2...v0.2.3) - 2026-10-16
|
|
11
22
|
|
|
12
23
|
### Fixed
|
|
@@ -1690,9 +1690,11 @@ mod tests {
|
|
|
1690
1690
|
.unwrap();
|
|
1691
1691
|
std::fs::set_permissions(&browser, std::fs::Permissions::from_mode(0o700)).unwrap();
|
|
1692
1692
|
let started = std::time::Instant::now();
|
|
1693
|
-
|
|
1693
|
+
// Fresh executable inspection on macOS can exceed 100 ms before the
|
|
1694
|
+
// script writes its PID. Allow startup while still bounding the hang.
|
|
1695
|
+
let error = browser_version_with_timeout(&browser, Duration::from_secs(1)).unwrap_err();
|
|
1694
1696
|
assert!(error.to_string().contains("timed out"));
|
|
1695
|
-
assert!(started.elapsed() < Duration::from_secs(
|
|
1697
|
+
assert!(started.elapsed() < Duration::from_secs(3));
|
|
1696
1698
|
let pid: i32 = std::fs::read_to_string(dir.path().join("browser.pid"))
|
|
1697
1699
|
.unwrap()
|
|
1698
1700
|
.parse()
|
|
@@ -10,7 +10,7 @@ use crate::error::AppError;
|
|
|
10
10
|
use serde_json::{Value, json};
|
|
11
11
|
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt};
|
|
12
12
|
|
|
13
|
-
pub const PROTOCOL: u32 =
|
|
13
|
+
pub const PROTOCOL: u32 = 5;
|
|
14
14
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
15
15
|
/// The commit this binary was built from (see `build.rs`), reported in `hello` for
|
|
16
16
|
/// diagnosis and never compared; empty when unknown.
|
|
@@ -71,7 +71,11 @@ pub fn messaging_unsupported() -> AppError {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
fn account_selection_error() -> AppError {
|
|
74
|
-
AppError::Desktop("
|
|
74
|
+
AppError::Desktop("Both personal and work/school Teams are open; use `init --backend desktop --desktop-account personal` or `--desktop-account work-school` to select one".into())
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn cached_accounts_error() -> AppError {
|
|
78
|
+
AppError::Desktop("Teams holds cached data for more than one account, organization or display language, and Teams does not record which one is signed in. For a work/school account, Microsoft Graph works instead: `teams init --backend graph`".into())
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
@@ -676,6 +680,7 @@ fn people_result(reply: &Value, create: bool) -> Result<Value, AppError> {
|
|
|
676
680
|
if let Some(code) = value["error"].as_str() {
|
|
677
681
|
return Err(match code {
|
|
678
682
|
"account_ambiguous" => account_selection_error(),
|
|
683
|
+
"cache_ambiguous" => cached_accounts_error(),
|
|
679
684
|
"permission_denied" => AppError::Permission("Teams policy does not allow a chat with this recipient".into()),
|
|
680
685
|
"search_failed" => AppError::Desktop("Teams could not resolve this person. Check sign-in and try their full email address".into()),
|
|
681
686
|
"layout" => AppError::Unsupported("This Teams version does not expose a supported recipient interface".into()),
|
|
@@ -784,6 +789,7 @@ fn send_error(code: &str) -> AppError {
|
|
|
784
789
|
match code {
|
|
785
790
|
"not_found" => AppError::NotFound("chat not found in the Teams desktop cache".into()),
|
|
786
791
|
"account_ambiguous" => account_selection_error(),
|
|
792
|
+
"cache_ambiguous" => cached_accounts_error(),
|
|
787
793
|
"unconfirmed" => AppError::Unconfirmed(
|
|
788
794
|
"Teams did not confirm the message; it may still be delivered. Check the chat before sending again".into(),
|
|
789
795
|
),
|
|
@@ -894,6 +900,9 @@ fn read_page(reply: &Value) -> Result<Page, AppError> {
|
|
|
894
900
|
if code == "account_ambiguous" {
|
|
895
901
|
return Err(account_selection_error());
|
|
896
902
|
}
|
|
903
|
+
if code == "cache_ambiguous" {
|
|
904
|
+
return Err(cached_accounts_error());
|
|
905
|
+
}
|
|
897
906
|
return Err(AppError::Desktop(match (status, code) {
|
|
898
907
|
(Some(status), _) => status.detail().into(),
|
|
899
908
|
(None, "no_target") => {
|
|
@@ -928,6 +937,9 @@ fn read_page(reply: &Value) -> Result<Page, AppError> {
|
|
|
928
937
|
if value["error"] == "account_ambiguous" {
|
|
929
938
|
return Err(account_selection_error());
|
|
930
939
|
}
|
|
940
|
+
if value["error"] == "cache_ambiguous" {
|
|
941
|
+
return Err(cached_accounts_error());
|
|
942
|
+
}
|
|
931
943
|
let items = value["items"].as_array().ok_or_else(invalid)?.clone();
|
|
932
944
|
let next_cursor = match &value["next"] {
|
|
933
945
|
Value::Null => None,
|
|
@@ -1219,7 +1231,7 @@ mod tests {
|
|
|
1219
1231
|
/// the new one; otherwise just update the expectations.
|
|
1220
1232
|
#[test]
|
|
1221
1233
|
fn the_protocol_is_pinned() {
|
|
1222
|
-
assert_eq!(helper::PROTOCOL,
|
|
1234
|
+
assert_eq!(helper::PROTOCOL, 5);
|
|
1223
1235
|
let cursor = encode_cursor(&json!({"t": 1, "id": "m1"})).unwrap();
|
|
1224
1236
|
assert_eq!(
|
|
1225
1237
|
read_request(9222, Some(NOTES), 20, Some(&cursor)).unwrap(),
|
|
@@ -1258,14 +1270,14 @@ mod tests {
|
|
|
1258
1270
|
.map(|b| format!("{b:02x}"))
|
|
1259
1271
|
.collect::<String>()
|
|
1260
1272
|
});
|
|
1261
|
-
// Protocol
|
|
1273
|
+
// Protocol 5 selects the signed-in cache and reports remaining ambiguity apart from surface selection.
|
|
1262
1274
|
assert_eq!(
|
|
1263
1275
|
scripts,
|
|
1264
1276
|
[
|
|
1265
1277
|
"32ca0293d2d5",
|
|
1266
1278
|
"baa0bc29c44b",
|
|
1267
|
-
"
|
|
1268
|
-
"
|
|
1279
|
+
"025a8edc46b8",
|
|
1280
|
+
"23b1a0e4b293"
|
|
1269
1281
|
]
|
|
1270
1282
|
);
|
|
1271
1283
|
}
|
|
@@ -1300,7 +1312,11 @@ mod tests {
|
|
|
1300
1312
|
));
|
|
1301
1313
|
assert!(matches!(
|
|
1302
1314
|
people_result(&page_reply(json!({"error":"account_ambiguous"})), false),
|
|
1303
|
-
Err(AppError::Desktop(
|
|
1315
|
+
Err(AppError::Desktop(m)) if m.contains("--desktop-account")
|
|
1316
|
+
));
|
|
1317
|
+
assert!(matches!(
|
|
1318
|
+
people_result(&page_reply(json!({"error":"cache_ambiguous"})), false),
|
|
1319
|
+
Err(AppError::Desktop(m)) if !m.contains("--desktop-account")
|
|
1304
1320
|
));
|
|
1305
1321
|
for id in [
|
|
1306
1322
|
"someone@example.invalid",
|
|
@@ -1469,6 +1485,7 @@ mod tests {
|
|
|
1469
1485
|
Err(AppError::NotFound(_))
|
|
1470
1486
|
));
|
|
1471
1487
|
let layout = json!({"result":{"exceptionDetails":{"exception":{"description":"Error: layout\n at x"}}}});
|
|
1488
|
+
// Mixed personal and work/school windows are resolved by choosing a surface.
|
|
1472
1489
|
for ambiguous in [
|
|
1473
1490
|
json!({"error": "account_ambiguous"}),
|
|
1474
1491
|
reply(json!({"error": "account_ambiguous"})),
|
|
@@ -1480,6 +1497,20 @@ mod tests {
|
|
|
1480
1497
|
matches!(sent_message(&ambiguous, CHAT), Err(AppError::Desktop(m)) if m.contains("--desktop-account"))
|
|
1481
1498
|
);
|
|
1482
1499
|
}
|
|
1500
|
+
// Several caches within one surface are not, so no flag is suggested.
|
|
1501
|
+
for ambiguous in [
|
|
1502
|
+
json!({"error": "cache_ambiguous"}),
|
|
1503
|
+
reply(json!({"error": "cache_ambiguous"})),
|
|
1504
|
+
] {
|
|
1505
|
+
for result in [
|
|
1506
|
+
read_page(&ambiguous).map(|_| ()),
|
|
1507
|
+
sent_message(&ambiguous, CHAT).map(|_| ()),
|
|
1508
|
+
] {
|
|
1509
|
+
assert!(
|
|
1510
|
+
matches!(result, Err(AppError::Desktop(m)) if m.contains("cached") && !m.contains("--desktop-account"))
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1483
1514
|
assert!(matches!(read_page(&layout), Err(AppError::Desktop(m)) if m.contains("layout")));
|
|
1484
1515
|
for reply in [
|
|
1485
1516
|
json!({"error": "no_target"}),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Select one account's cache databases. Several account caches can coexist in a
|
|
2
|
+
// storage context; their records must never be joined or sent through arbitrarily.
|
|
3
|
+
all => {
|
|
4
|
+
const candidates = kind => all.filter(db =>
|
|
5
|
+
typeof db.name === 'string' && db.name.includes(':' + kind + ':'));
|
|
6
|
+
// Teams names caches `<prefix>:<kind>:<client>:<tenant>:<user>:<locale>`. Other
|
|
7
|
+
// accounts, guest organizations and earlier display languages leave their own; the
|
|
8
|
+
// signed-in one is what Teams itself records, and anything less agreed is refused.
|
|
9
|
+
const signedIn = () => {
|
|
10
|
+
try {
|
|
11
|
+
const item = key => JSON.parse(localStorage.getItem(key) ?? 'null');
|
|
12
|
+
const user = item('tmp.auth.v1.GLOBAL.PrimaryUserId.PrimaryUserId')?.item;
|
|
13
|
+
const primary = item('tmp.react-web-client.cachedPrimaryUser');
|
|
14
|
+
if (typeof user !== 'string' || primary?.userId !== user || typeof primary.tenantId !== 'string') return null;
|
|
15
|
+
const locale = localStorage.getItem(`tmp.${primary.tenantId}.${user}.react-web-client.applicationLocaleCode`);
|
|
16
|
+
return { tenant: primary.tenantId, user, locale };
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
let conversations = candidates('conversation-manager');
|
|
22
|
+
if (conversations.length > 1) {
|
|
23
|
+
const active = signedIn();
|
|
24
|
+
const part = (db, index) => db.name.split(':')[index];
|
|
25
|
+
if (active) conversations = conversations.filter(db =>
|
|
26
|
+
part(db, 3) === active.tenant && part(db, 4) === active.user);
|
|
27
|
+
if (active && conversations.length > 1) conversations = conversations.filter(db => part(db, 5) === active.locale);
|
|
28
|
+
if (conversations.length !== 1) return { error: 'cache_ambiguous' };
|
|
29
|
+
}
|
|
30
|
+
if (conversations.length !== 1) return { error: 'layout' };
|
|
31
|
+
const conversation = conversations[0].name;
|
|
32
|
+
const account = conversation.split(':')[4];
|
|
33
|
+
if (!account) return { error: 'layout' };
|
|
34
|
+
const related = kind => {
|
|
35
|
+
const sibling = conversation.replace(':conversation-manager:', ':' + kind + ':');
|
|
36
|
+
if (all.some(db => db.name === sibling)) return sibling;
|
|
37
|
+
const matches = candidates(kind).filter(db => db.name.split(':')[4] === account);
|
|
38
|
+
return matches.length === 1 ? matches[0].name : null;
|
|
39
|
+
};
|
|
40
|
+
return { account, conversation, replies: related('replychain-manager'), profiles: related('profiles') };
|
|
41
|
+
}
|
|
@@ -281,7 +281,7 @@ async (p, select) => {
|
|
|
281
281
|
} finally {
|
|
282
282
|
db?.close();
|
|
283
283
|
}
|
|
284
|
-
if (own.length !== 1) return { error: own.length ? "
|
|
284
|
+
if (own.length !== 1) return { error: own.length ? "cache_ambiguous" : "layout" };
|
|
285
285
|
if (own[0] === p.recipient) return {id:"48:notes",chatType:"oneOnOne",members:[{userId:own[0]}],source:"desktop"};
|
|
286
286
|
const userIds = [...new Set([own[0], p.recipient])];
|
|
287
287
|
const lookup = operationDoc(
|
|
@@ -231,10 +231,50 @@ test('a missing account database never falls back to another account', async ()
|
|
|
231
231
|
assert.ok(f.opened.every(database => database.endsWith(':account-a')));
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
+
function selectWith(names, storage = {}) {
|
|
235
|
+
const context = vm.createContext({ localStorage: { getItem: key => key in storage ? storage[key] : null } });
|
|
236
|
+
// Compare plain values: objects from another context have their own prototypes.
|
|
237
|
+
return JSON.parse(JSON.stringify(vm.runInContext(source('desktop_cache.js'), context)(names.map(name => ({ name })))));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const teams = (kind, tenant, user, locale = 'en-us') => `Teams:${kind}:react-web-client:${tenant}:${user}:${locale}`;
|
|
241
|
+
const signedIn = (tenant, user, locale) => ({
|
|
242
|
+
'tmp.auth.v1.GLOBAL.PrimaryUserId.PrimaryUserId': JSON.stringify({ item: user }),
|
|
243
|
+
'tmp.react-web-client.cachedPrimaryUser': JSON.stringify({ tenantId: tenant, userId: user }),
|
|
244
|
+
...(locale && { [`tmp.${tenant}.${user}.react-web-client.applicationLocaleCode`]: locale }),
|
|
245
|
+
});
|
|
246
|
+
const caches = (tenant, user, locale) => ['conversation-manager', 'replychain-manager', 'profiles']
|
|
247
|
+
.map(kind => teams(kind, tenant, user, locale));
|
|
248
|
+
|
|
249
|
+
test('the signed-in user, tenant and display language select among coexisting caches', () => {
|
|
250
|
+
const names = [
|
|
251
|
+
...caches('home', 'me', 'en-us'), ...caches('home', 'me', 'nl-nl'),
|
|
252
|
+
...caches('guest', 'me-as-guest'), ...caches('home', 'colleague'),
|
|
253
|
+
];
|
|
254
|
+
for (const [tenant, user, locale] of [['home', 'me', 'nl-nl'], ['home', 'me', 'en-us'], ['guest', 'me-as-guest', 'en-us']]) {
|
|
255
|
+
assert.deepEqual(selectWith(names, signedIn(tenant, user, locale)), {
|
|
256
|
+
account: user, conversation: teams('conversation-manager', tenant, user, locale),
|
|
257
|
+
replies: teams('replychain-manager', tenant, user, locale), profiles: teams('profiles', tenant, user, locale),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// Only one cache for the signed-in account needs no display language.
|
|
261
|
+
assert.equal(selectWith(names, signedIn('home', 'colleague')).conversation, teams('conversation-manager', 'home', 'colleague'));
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('caches are refused when Teams does not identify exactly one of them', () => {
|
|
265
|
+
const names = [...caches('home', 'me', 'en-us'), ...caches('home', 'me', 'nl-nl'), ...caches('home', 'colleague')];
|
|
266
|
+
const inconsistent = { ...signedIn('home', 'me', 'en-us'),
|
|
267
|
+
'tmp.react-web-client.cachedPrimaryUser': JSON.stringify({ tenantId: 'home', userId: 'colleague' }) };
|
|
268
|
+
for (const storage of [{}, signedIn('home', 'me'), signedIn('home', 'me', 'de-de'), signedIn('home', 'stranger', 'en-us'), inconsistent,
|
|
269
|
+
{ 'tmp.auth.v1.GLOBAL.PrimaryUserId.PrimaryUserId': 'not json' }]) {
|
|
270
|
+
assert.deepEqual(selectWith(names, storage), { error: 'cache_ambiguous' });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
234
274
|
test('ambiguous account caches are refused before reading or sending', async () => {
|
|
235
275
|
const f = fixture({ ambiguous: true });
|
|
236
|
-
assert.equal((await f.read()).error, '
|
|
237
|
-
assert.equal((await f.send()).error, '
|
|
276
|
+
assert.equal((await f.read()).error, 'cache_ambiguous');
|
|
277
|
+
assert.equal((await f.send()).error, 'cache_ambiguous');
|
|
238
278
|
assert.deepEqual(f.opened, []);
|
|
239
279
|
assert.deepEqual(f.mutations, []);
|
|
240
280
|
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// Select one account's cache databases. Several account caches can coexist in a
|
|
2
|
-
// storage context; their records must never be joined or sent through arbitrarily.
|
|
3
|
-
all => {
|
|
4
|
-
const candidates = kind => all.filter(db =>
|
|
5
|
-
typeof db.name === 'string' && db.name.includes(':' + kind + ':'));
|
|
6
|
-
const conversations = candidates('conversation-manager');
|
|
7
|
-
if (conversations.length > 1) return { error: 'account_ambiguous' };
|
|
8
|
-
if (conversations.length !== 1) return { error: 'layout' };
|
|
9
|
-
const conversation = conversations[0].name;
|
|
10
|
-
const account = conversation.split(':')[4];
|
|
11
|
-
if (!account) return { error: 'layout' };
|
|
12
|
-
const related = kind => {
|
|
13
|
-
const matches = candidates(kind).filter(db => db.name.split(':')[4] === account);
|
|
14
|
-
return matches.length === 1 ? matches[0].name : null;
|
|
15
|
-
};
|
|
16
|
-
return { account, conversation, replies: related('replychain-manager'), profiles: related('profiles') };
|
|
17
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|