@aglyn/tenant-feature-instance 1.0.0-beta.196 → 1.0.0-beta.198
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/tenant-feature-instance",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.198",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.198",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.198",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.198",
|
|
31
|
+
"@aglyn/shared-util-http": "1.0.0-beta.198",
|
|
32
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.198",
|
|
33
33
|
"@swc/helpers": "0.5.23",
|
|
34
34
|
"lodash-es": "^4.18.1"
|
|
35
35
|
},
|
|
@@ -23,6 +23,12 @@ export interface ScopeTokensState {
|
|
|
23
23
|
* Defaults to org-wide while loading: the rules are the enforcement point,
|
|
24
24
|
* and guessing "scoped" before the member doc arrives would flash an empty
|
|
25
25
|
* library at people who can see everything.
|
|
26
|
+
*
|
|
27
|
+
* `loaded` answers for the member and org ASKED ABOUT NOW. A caller whose org
|
|
28
|
+
* id arrives after its first render — resolved from a site through the host
|
|
29
|
+
* index — would otherwise read the previous pass's answer, org-wide and
|
|
30
|
+
* loaded, while the member document it actually needs is still in flight,
|
|
31
|
+
* and send the one unfiltered query this hook exists to prevent.
|
|
26
32
|
*/
|
|
27
33
|
export declare function useScopeTokens(orgId: string | undefined): ScopeTokensState;
|
|
28
34
|
export default useScopeTokens;
|
|
@@ -17,7 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/ import { isOrgWideMember, memberScopeTokens, ORG_SCOPE_TOKEN } from "@aglyn/aglyn";
|
|
19
19
|
import { doc, getDoc } from "firebase/firestore";
|
|
20
|
-
import { useEffect, useState } from "react";
|
|
20
|
+
import { useEffect, useMemo, useState } from "react";
|
|
21
21
|
import { useFirestore, useUser } from "./firebase/firebase-services.js";
|
|
22
22
|
/**
|
|
23
23
|
* The signed-in user's scope tokens in an org (AGL-1044/1045).
|
|
@@ -37,15 +37,24 @@ import { useFirestore, useUser } from "./firebase/firebase-services.js";
|
|
|
37
37
|
* Defaults to org-wide while loading: the rules are the enforcement point,
|
|
38
38
|
* and guessing "scoped" before the member doc arrives would flash an empty
|
|
39
39
|
* library at people who can see everything.
|
|
40
|
+
*
|
|
41
|
+
* `loaded` answers for the member and org ASKED ABOUT NOW. A caller whose org
|
|
42
|
+
* id arrives after its first render — resolved from a site through the host
|
|
43
|
+
* index — would otherwise read the previous pass's answer, org-wide and
|
|
44
|
+
* loaded, while the member document it actually needs is still in flight,
|
|
45
|
+
* and send the one unfiltered query this hook exists to prevent.
|
|
40
46
|
*/ export function useScopeTokens(orgId) {
|
|
41
47
|
const { data: user } = useUser();
|
|
42
48
|
const firestore = useFirestore();
|
|
49
|
+
const signedIn = user == null ? void 0 : user.uid;
|
|
50
|
+
/** Whose reach the state holds: `uid/orgId`, or `null` for no org. */ const asked = signedIn && orgId ? `${signedIn}/${orgId}` : null;
|
|
43
51
|
const [state, setState] = useState({
|
|
44
52
|
tokens: [
|
|
45
53
|
ORG_SCOPE_TOKEN
|
|
46
54
|
],
|
|
47
55
|
orgWide: true,
|
|
48
|
-
loaded: false
|
|
56
|
+
loaded: false,
|
|
57
|
+
for: null
|
|
49
58
|
});
|
|
50
59
|
useEffect(()=>{
|
|
51
60
|
const uid = user == null ? void 0 : user.uid;
|
|
@@ -55,10 +64,12 @@ import { useFirestore, useUser } from "./firebase/firebase-services.js";
|
|
|
55
64
|
ORG_SCOPE_TOKEN
|
|
56
65
|
],
|
|
57
66
|
orgWide: true,
|
|
58
|
-
loaded: Boolean(uid)
|
|
67
|
+
loaded: Boolean(uid),
|
|
68
|
+
for: null
|
|
59
69
|
});
|
|
60
70
|
return;
|
|
61
71
|
}
|
|
72
|
+
const key = `${uid}/${orgId}`;
|
|
62
73
|
let cancelled = false;
|
|
63
74
|
getDoc(doc(firestore, 'orgs', orgId, 'members', uid)).then((snapshot)=>{
|
|
64
75
|
if (cancelled) return;
|
|
@@ -72,7 +83,8 @@ import { useFirestore, useUser } from "./firebase/firebase-services.js";
|
|
|
72
83
|
setState({
|
|
73
84
|
tokens: memberScopeTokens(member),
|
|
74
85
|
orgWide: isOrgWideMember(member),
|
|
75
|
-
loaded: true
|
|
86
|
+
loaded: true,
|
|
87
|
+
for: key
|
|
76
88
|
});
|
|
77
89
|
}).catch(()=>{
|
|
78
90
|
if (!cancelled) {
|
|
@@ -81,7 +93,8 @@ import { useFirestore, useUser } from "./firebase/firebase-services.js";
|
|
|
81
93
|
ORG_SCOPE_TOKEN
|
|
82
94
|
],
|
|
83
95
|
orgWide: true,
|
|
84
|
-
loaded: true
|
|
96
|
+
loaded: true,
|
|
97
|
+
for: key
|
|
85
98
|
});
|
|
86
99
|
}
|
|
87
100
|
});
|
|
@@ -93,7 +106,16 @@ import { useFirestore, useUser } from "./firebase/firebase-services.js";
|
|
|
93
106
|
orgId,
|
|
94
107
|
user
|
|
95
108
|
]);
|
|
96
|
-
|
|
109
|
+
const loaded = state.loaded && state.for === asked;
|
|
110
|
+
return useMemo(()=>({
|
|
111
|
+
tokens: state.tokens,
|
|
112
|
+
orgWide: state.orgWide,
|
|
113
|
+
loaded
|
|
114
|
+
}), [
|
|
115
|
+
state.tokens,
|
|
116
|
+
state.orgWide,
|
|
117
|
+
loaded
|
|
118
|
+
]);
|
|
97
119
|
}
|
|
98
120
|
export default useScopeTokens;
|
|
99
121
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/feature/instance/src/lib/hooks/use-scope-tokens.ts"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n isOrgWideMember,\n memberScopeTokens,\n ORG_SCOPE_TOKEN,\n type AglynOrgMember,\n} from '@aglyn/aglyn'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport { useFirestore, useUser } from './firebase/firebase-services'\n\nexport interface ScopeTokensState {\n /** The caller's read set, for `array-contains-any` (AGL-1037). */\n tokens: string[]\n /** Org-wide members read everything and need no query filter. */\n orgWide: boolean\n loaded: boolean\n}\n\n/**\n * The signed-in user's scope tokens in an org (AGL-1044/1045).\n *\n * This is NOT a UI-gating convenience — it is required for correctness.\n * Under AGL-1041/1042 the rules evaluate per document on a LIST, and\n * Firestore rejects the WHOLE query if any candidate would fail. So a\n * scoped collaborator's unfiltered `collection(...)` read comes back\n * `permission-denied`, not filtered: without the matching\n * `array-contains-any` constraint the page does not show less, it shows\n * an error.\n *\n * Org-wide members deliberately get `orgWide: true` and no filter. Adding\n * one would cost them a composite index for no benefit, and would silently\n * hide any doc whose `visibleTo` the backfill has not reached.\n *\n * Defaults to org-wide while loading: the rules are the enforcement point,\n * and guessing \"scoped\" before the member doc arrives would flash an empty\n * library at people who can see everything.\n */\nexport function useScopeTokens(orgId: string | undefined): ScopeTokensState {\n const { data: user } = useUser()\n const firestore = useFirestore()\n const [state, setState] = useState<ScopeTokensState>({\n tokens: [ORG_SCOPE_TOKEN],\n orgWide: true,\n loaded: false,\n })\n\n useEffect(() => {\n const uid = (user as { uid?: string } | undefined)?.uid\n if (!uid || !orgId) {\n setState({
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/feature/instance/src/lib/hooks/use-scope-tokens.ts"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n isOrgWideMember,\n memberScopeTokens,\n ORG_SCOPE_TOKEN,\n type AglynOrgMember,\n} from '@aglyn/aglyn'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useFirestore, useUser } from './firebase/firebase-services'\n\nexport interface ScopeTokensState {\n /** The caller's read set, for `array-contains-any` (AGL-1037). */\n tokens: string[]\n /** Org-wide members read everything and need no query filter. */\n orgWide: boolean\n loaded: boolean\n}\n\n/**\n * The signed-in user's scope tokens in an org (AGL-1044/1045).\n *\n * This is NOT a UI-gating convenience — it is required for correctness.\n * Under AGL-1041/1042 the rules evaluate per document on a LIST, and\n * Firestore rejects the WHOLE query if any candidate would fail. So a\n * scoped collaborator's unfiltered `collection(...)` read comes back\n * `permission-denied`, not filtered: without the matching\n * `array-contains-any` constraint the page does not show less, it shows\n * an error.\n *\n * Org-wide members deliberately get `orgWide: true` and no filter. Adding\n * one would cost them a composite index for no benefit, and would silently\n * hide any doc whose `visibleTo` the backfill has not reached.\n *\n * Defaults to org-wide while loading: the rules are the enforcement point,\n * and guessing \"scoped\" before the member doc arrives would flash an empty\n * library at people who can see everything.\n *\n * `loaded` answers for the member and org ASKED ABOUT NOW. A caller whose org\n * id arrives after its first render — resolved from a site through the host\n * index — would otherwise read the previous pass's answer, org-wide and\n * loaded, while the member document it actually needs is still in flight,\n * and send the one unfiltered query this hook exists to prevent.\n */\nexport function useScopeTokens(orgId: string | undefined): ScopeTokensState {\n const { data: user } = useUser()\n const firestore = useFirestore()\n const signedIn = (user as { uid?: string } | undefined)?.uid\n /** Whose reach the state holds: `uid/orgId`, or `null` for no org. */\n const asked = signedIn && orgId ? `${signedIn}/${orgId}` : null\n const [state, setState] = useState<ScopeTokensState & { for: string | null }>({\n tokens: [ORG_SCOPE_TOKEN],\n orgWide: true,\n loaded: false,\n for: null,\n })\n\n useEffect(() => {\n const uid = (user as { uid?: string } | undefined)?.uid\n if (!uid || !orgId) {\n setState({\n tokens: [ORG_SCOPE_TOKEN],\n orgWide: true,\n loaded: Boolean(uid),\n for: null,\n })\n return\n }\n const key = `${uid}/${orgId}`\n let cancelled = false\n getDoc(doc(firestore, 'orgs', orgId, 'members', uid))\n .then((snapshot) => {\n if (cancelled) return\n const member = snapshot.exists()\n ? ({ $id: uid, ...snapshot.data() } as Partial<AglynOrgMember>)\n : undefined\n // `memberScopeTokens` owns the \"stored projection, else recompute\"\n // rule so the client and every Admin-SDK gate answer it the same\n // way — a member doc the AGL-1040 backfill has not reached must\n // still resolve to what `grantHostAccess` would have stamped.\n setState({\n tokens: memberScopeTokens(member),\n orgWide: isOrgWideMember(member),\n loaded: true,\n for: key,\n })\n })\n .catch(() => {\n if (!cancelled) {\n setState({\n tokens: [ORG_SCOPE_TOKEN],\n orgWide: true,\n loaded: true,\n for: key,\n })\n }\n })\n return () => {\n cancelled = true\n }\n }, [firestore, orgId, user])\n\n const loaded = state.loaded && state.for === asked\n return useMemo(\n () => ({ tokens: state.tokens, orgWide: state.orgWide, loaded }),\n [state.tokens, state.orgWide, loaded],\n )\n}\n\nexport default useScopeTokens\n"],"names":["isOrgWideMember","memberScopeTokens","ORG_SCOPE_TOKEN","doc","getDoc","useEffect","useMemo","useState","useFirestore","useUser","useScopeTokens","orgId","data","user","firestore","signedIn","uid","asked","state","setState","tokens","orgWide","loaded","for","Boolean","key","cancelled","then","snapshot","member","exists","$id","undefined","catch"],"mappings":"AAAA;;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,eAAe,EACfC,iBAAiB,EACjBC,eAAe,QAEV,eAAc;AACrB,SAASC,GAAG,EAAEC,MAAM,QAAQ,qBAAoB;AAChD,SAASC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACpD,SAASC,YAAY,EAAEC,OAAO,QAAQ,kCAA8B;AAUpE;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASC,eAAeC,KAAyB;IACtD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGJ;IACvB,MAAMK,YAAYN;IAClB,MAAMO,WAAYF,wBAAD,AAACA,KAAuCG,GAAG;IAC5D,oEAAoE,GACpE,MAAMC,QAAQF,YAAYJ,QAAQ,GAAGI,SAAS,CAAC,EAAEJ,OAAO,GAAG;IAC3D,MAAM,CAACO,OAAOC,SAAS,GAAGZ,SAAoD;QAC5Ea,QAAQ;YAAClB;SAAgB;QACzBmB,SAAS;QACTC,QAAQ;QACRC,KAAK;IACP;IAEAlB,UAAU;QACR,MAAMW,MAAOH,wBAAD,AAACA,KAAuCG,GAAG;QACvD,IAAI,CAACA,OAAO,CAACL,OAAO;YAClBQ,SAAS;gBACPC,QAAQ;oBAAClB;iBAAgB;gBACzBmB,SAAS;gBACTC,QAAQE,QAAQR;gBAChBO,KAAK;YACP;YACA;QACF;QACA,MAAME,MAAM,GAAGT,IAAI,CAAC,EAAEL,OAAO;QAC7B,IAAIe,YAAY;QAChBtB,OAAOD,IAAIW,WAAW,QAAQH,OAAO,WAAWK,MAC7CW,IAAI,CAAC,CAACC;YACL,IAAIF,WAAW;YACf,MAAMG,SAASD,SAASE,MAAM,KACzB;gBAAEC,KAAKf;eAAQY,SAAShB,IAAI,MAC7BoB;YACJ,mEAAmE;YACnE,iEAAiE;YACjE,gEAAgE;YAChE,8DAA8D;YAC9Db,SAAS;gBACPC,QAAQnB,kBAAkB4B;gBAC1BR,SAASrB,gBAAgB6B;gBACzBP,QAAQ;gBACRC,KAAKE;YACP;QACF,GACCQ,KAAK,CAAC;YACL,IAAI,CAACP,WAAW;gBACdP,SAAS;oBACPC,QAAQ;wBAAClB;qBAAgB;oBACzBmB,SAAS;oBACTC,QAAQ;oBACRC,KAAKE;gBACP;YACF;QACF;QACF,OAAO;YACLC,YAAY;QACd;IACF,GAAG;QAACZ;QAAWH;QAAOE;KAAK;IAE3B,MAAMS,SAASJ,MAAMI,MAAM,IAAIJ,MAAMK,GAAG,KAAKN;IAC7C,OAAOX,QACL,IAAO,CAAA;YAAEc,QAAQF,MAAME,MAAM;YAAEC,SAASH,MAAMG,OAAO;YAAEC;QAAO,CAAA,GAC9D;QAACJ,MAAME,MAAM;QAAEF,MAAMG,OAAO;QAAEC;KAAO;AAEzC;AAEA,eAAeZ,eAAc"}
|