@workos-inc/widgets 1.21.0 → 1.21.1-next.1794335822786

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.21.1-next.1794335822786
4
+
5
+ ### Patch Changes
6
+
7
+ - Sync generated API from upstream schema.
8
+
9
+ - Refetch widget data when an `authToken` function resolves to a token for a different session, user or organization.
10
+
3
11
  ## 1.21.0
4
12
 
5
13
  ### Patch Changes
@@ -39,8 +39,7 @@ const ApiProvider = ({ authToken, baseUrl, children, widgetType }) => {
39
39
  });
40
40
  react.useEffect(() => {
41
41
  if (typeof authToken !== "string") return;
42
- const cached = queryClient.getQueryData(queryKey);
43
- if (cached?.authToken === authToken) return;
42
+ if (queryClient.getQueryData(queryKey)?.authToken === authToken) return;
44
43
  queryClient.cancelQueries({ queryKey });
45
44
  const claims = require_api_claims.getClaims(authToken);
46
45
  queryClient.setQueryData(queryKey, {
@@ -48,12 +47,38 @@ const ApiProvider = ({ authToken, baseUrl, children, widgetType }) => {
48
47
  permissions: claims.permissions,
49
48
  exp: claims.exp
50
49
  });
51
- if (cached && !isSameSubject(require_api_claims.getClaims(cached.authToken), claims)) queryClient.invalidateQueries({ predicate: (query) => require_api_client_graphql_query_keys.isClientGraphqlQueryKey(query.queryKey) });
52
50
  }, [
53
51
  authToken,
54
52
  queryClient,
55
53
  queryKey
56
54
  ]);
55
+ const currentToken = typeof authToken === "string" ? authToken : authTokenQuery.data?.authToken;
56
+ const currentIdentityKey = react.useMemo(() => currentToken ? getIdentityKey(require_api_claims.getClaims(currentToken)) : void 0, [currentToken]);
57
+ const [tokenIdentity, setTokenIdentity] = react.useState({
58
+ key: currentIdentityKey,
59
+ generation: 0
60
+ });
61
+ if (currentIdentityKey !== tokenIdentity.key) setTokenIdentity({
62
+ key: currentIdentityKey,
63
+ generation: tokenIdentity.key === void 0 ? tokenIdentity.generation : tokenIdentity.generation + 1
64
+ });
65
+ const [settledGeneration, setSettledGeneration] = react.useState(0);
66
+ const identityGeneration = tokenIdentity.generation;
67
+ const isIdentityPending = settledGeneration !== identityGeneration;
68
+ react.useEffect(() => {
69
+ if (!isIdentityPending) return;
70
+ queryClient.invalidateQueries({ predicate: isClientGraphqlQuery });
71
+ const settleWhenRefetched = () => {
72
+ if (!queryClient.getQueryCache().findAll({ predicate: isClientGraphqlQuery }).some((query) => query.state.fetchStatus === "fetching" || query.isActive() && query.state.isInvalidated)) setSettledGeneration(identityGeneration);
73
+ };
74
+ const unsubscribe = queryClient.getQueryCache().subscribe(settleWhenRefetched);
75
+ settleWhenRefetched();
76
+ return unsubscribe;
77
+ }, [
78
+ identityGeneration,
79
+ isIdentityPending,
80
+ queryClient
81
+ ]);
57
82
  const exp = authTokenQuery.data?.exp;
58
83
  react.useEffect(() => {
59
84
  if (!exp) return;
@@ -89,21 +114,30 @@ const ApiProvider = ({ authToken, baseUrl, children, widgetType }) => {
89
114
  baseUrl,
90
115
  elevatedAccess,
91
116
  setElevatedAccess,
92
- widgetType
117
+ widgetType,
118
+ isIdentityPending
93
119
  }), [
94
120
  authTokenQuery.data,
95
121
  baseUrl,
96
122
  elevatedAccess,
97
123
  setElevatedAccess,
98
- widgetType
124
+ widgetType,
125
+ isIdentityPending
99
126
  ]);
100
127
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApiContext.Provider, {
101
128
  value,
102
129
  children
103
130
  });
104
131
  };
105
- function isSameSubject(a, b) {
106
- return a.sid === b.sid && a.sub === b.sub && a.org_id === b.org_id;
132
+ function isClientGraphqlQuery(query) {
133
+ return require_api_client_graphql_query_keys.isClientGraphqlQueryKey(query.queryKey);
134
+ }
135
+ function getIdentityKey(claims) {
136
+ return JSON.stringify([
137
+ claims.sid,
138
+ claims.sub,
139
+ claims.org_id
140
+ ]);
107
141
  }
108
142
  const useApi = () => {
109
143
  const context = react.useContext(ApiContext);
@@ -1 +1 @@
1
- {"version":3,"file":"api-provider.cjs","names":["React","useQueryClient","useQuery","getClaims","isClientGraphqlQueryKey","useIsHydrated"],"sources":["../../../src/api/api-provider.tsx"],"sourcesContent":["import { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport * as React from \"react\";\nimport { useIsHydrated } from \"../lib/use-is-hydrated.js\";\nimport { getClaims } from \"./claims.js\";\nimport type { Claims } from \"./claims.js\";\nimport { isClientGraphqlQueryKey } from \"./client-graphql/query-keys.js\";\n\ntype WidgetType =\n | \"admin-portal-audit-log-streaming\"\n | \"admin-portal-domain-verification\"\n | \"user-management\"\n | \"user-api-keys\"\n | \"organization-switcher\"\n | \"user-sessions\"\n | \"user-security\"\n | \"user-profile\"\n | \"admin-portal-sso-connection\"\n | \"api-keys\"\n | \"pipes\"\n | \"pipes-admin\"\n | \"directory-sync\";\n\ntype AuthToken = string | (() => Promise<string>);\n\ninterface ElevatedAccess {\n token: string;\n expiresAt: string;\n}\n\ninterface ApiContextValue {\n authToken: string | undefined;\n permissions: string[] | undefined;\n exp: number | undefined;\n baseUrl: string;\n widgetType: WidgetType;\n elevatedAccess?: ElevatedAccess;\n setElevatedAccess: (elevatedAccess?: ElevatedAccess) => void;\n}\n\ninterface AuthTokenQueryData {\n authToken: string;\n permissions: string[] | undefined;\n exp: number | undefined;\n}\n\nconst authTokenQueryKey = (widgetType: WidgetType) =>\n [\"authToken\", widgetType] as const;\n\n// Refetch this many ms before the JWT expires so data queries always see a\n// fresh token.\nconst TOKEN_REFRESH_BUFFER_MS = 30_000;\n\n// Cap setTimeout delays at ~24 days to stay well below the 32-bit signed int\n// limit (~24.8 days), which some runtimes silently coerce to 1ms.\nconst MAX_TIMEOUT_MS = 2_147_483_000;\n\nconst ApiContext = React.createContext<ApiContextValue | undefined>(undefined);\nApiContext.displayName = \"ApiContext\";\n\ninterface ApiProviderProps {\n authToken: AuthToken;\n baseUrl: string;\n children: React.ReactNode;\n widgetType: WidgetType;\n}\n\nconst ApiProvider: React.FC<ApiProviderProps> = ({\n authToken,\n baseUrl,\n children,\n widgetType,\n}) => {\n const queryClient = useQueryClient();\n const queryKey = authTokenQueryKey(widgetType);\n const authTokenQuery = useQuery<AuthTokenQueryData>({\n initialData: () => {\n if (typeof authToken !== \"string\") {\n return undefined;\n }\n\n const claims = getClaims(authToken);\n return {\n authToken,\n permissions: claims.permissions,\n exp: claims.exp,\n };\n },\n queryFn: async () => {\n const resolvedToken =\n typeof authToken === \"string\" ? authToken : await authToken();\n const claims = getClaims(resolvedToken);\n\n return {\n authToken: resolvedToken,\n permissions: claims.permissions,\n exp: claims.exp,\n };\n },\n queryKey,\n });\n\n // `initialData` only seeds the cache on first mount, so a new string token\n // has to be written to the cache explicitly. A token for a different session,\n // user or organization (e.g. after switching organizations) also invalidates\n // data fetched with the previous token.\n React.useEffect(() => {\n if (typeof authToken !== \"string\") {\n return;\n }\n\n const cached = queryClient.getQueryData<AuthTokenQueryData>(queryKey);\n if (cached?.authToken === authToken) {\n return;\n }\n\n // Keep an in-flight fetch holding the previous token from overwriting the\n // new one.\n void queryClient.cancelQueries({ queryKey });\n const claims = getClaims(authToken);\n queryClient.setQueryData<AuthTokenQueryData>(queryKey, {\n authToken,\n permissions: claims.permissions,\n exp: claims.exp,\n });\n\n if (cached && !isSameSubject(getClaims(cached.authToken), claims)) {\n void queryClient.invalidateQueries({\n predicate: (query) => isClientGraphqlQueryKey(query.queryKey),\n });\n }\n }, [authToken, queryClient, queryKey]);\n\n // Keep the token \"fresh\" until ~30s before it expires, so window-focus\n // re-fetches re-mint before data queries use a stale Bearer.\n //\n // TODO(v2): prefer the function-form `staleTime` on `useQuery` above once we\n // can bump the `@tanstack/react-query` peer dep to ^5.44.0 (the version that\n // introduced it). That lets react-query own the freshness window and avoids\n // the extra timer + window-focus nonsense this effect introduces.\n const exp = authTokenQuery.data?.exp;\n React.useEffect(() => {\n if (!exp) {\n return;\n }\n\n const delay = Math.min(\n MAX_TIMEOUT_MS,\n exp * 1000 - Date.now() - TOKEN_REFRESH_BUFFER_MS,\n );\n if (delay <= 0) {\n void queryClient.refetchQueries({\n queryKey: authTokenQueryKey(widgetType),\n });\n return;\n }\n const timeout = window.setTimeout(() => {\n void queryClient.refetchQueries({\n queryKey: authTokenQueryKey(widgetType),\n });\n }, delay);\n return () => window.clearTimeout(timeout);\n }, [exp, queryClient, widgetType]);\n\n const [elevatedAccess, setElevatedAccess] = React.useState<ElevatedAccess>();\n const elevatedAccessTimeout = React.useRef<number | undefined>(undefined);\n\n // This effect manages the expiration of elevated access tokens\n // When an elevated access token is present, it checks every 30 seconds if the token has expired\n // If the token has expired (current time > expiration time), it clears the elevated access\n React.useEffect(() => {\n if (elevatedAccessTimeout.current) {\n window.clearInterval(elevatedAccessTimeout.current);\n }\n\n if (elevatedAccess) {\n elevatedAccessTimeout.current = window.setInterval(() => {\n const now = new Date();\n const expiresAtDate = new Date(elevatedAccess.expiresAt);\n\n // Reset the elevated access if it has expired\n if (now > expiresAtDate) {\n setElevatedAccess(undefined);\n }\n }, 30_000); // every 30 seconds\n }\n\n return () => {\n if (elevatedAccessTimeout.current) {\n window.clearInterval(elevatedAccessTimeout.current);\n }\n };\n }, [elevatedAccess]);\n\n const value = React.useMemo(\n () => ({\n authToken: authTokenQuery.data?.authToken,\n permissions: authTokenQuery.data?.permissions,\n exp: authTokenQuery.data?.exp,\n baseUrl,\n elevatedAccess,\n setElevatedAccess,\n widgetType,\n }),\n [\n authTokenQuery.data,\n baseUrl,\n elevatedAccess,\n setElevatedAccess,\n widgetType,\n ],\n );\n\n return <ApiContext.Provider value={value}>{children}</ApiContext.Provider>;\n};\n\nfunction isSameSubject(a: Claims, b: Claims) {\n return a.sid === b.sid && a.sub === b.sub && a.org_id === b.org_id;\n}\n\nconst useApi = () => {\n const context = React.useContext(ApiContext);\n\n if (context === undefined) {\n throw new Error(\"useApi must be used within an ApiProvider\");\n }\n\n return context;\n};\n\nconst useElevatedAccessToken = () => {\n const { elevatedAccess, setElevatedAccess } = useApi();\n\n return { elevatedAccess, setElevatedAccess };\n};\n\nconst useApiReady = () => {\n const { authToken } = useApi();\n const isHydrated = useIsHydrated();\n\n return isHydrated && authToken !== undefined;\n};\nexport type { WidgetType, AuthToken, AuthTokenQueryData };\nexport {\n authTokenQueryKey,\n ApiProvider,\n useApi,\n useElevatedAccessToken,\n useApiReady,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"api-provider.cjs","names":["React","useQueryClient","useQuery","getClaims","isClientGraphqlQueryKey","useIsHydrated"],"sources":["../../../src/api/api-provider.tsx"],"sourcesContent":["import { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport type { Query } from \"@tanstack/react-query\";\nimport * as React from \"react\";\nimport { useIsHydrated } from \"../lib/use-is-hydrated.js\";\nimport { getClaims } from \"./claims.js\";\nimport type { Claims } from \"./claims.js\";\nimport { isClientGraphqlQueryKey } from \"./client-graphql/query-keys.js\";\n\ntype WidgetType =\n | \"admin-portal-audit-log-streaming\"\n | \"admin-portal-domain-verification\"\n | \"user-management\"\n | \"user-api-keys\"\n | \"organization-switcher\"\n | \"user-sessions\"\n | \"user-security\"\n | \"user-profile\"\n | \"admin-portal-sso-connection\"\n | \"api-keys\"\n | \"pipes\"\n | \"pipes-admin\"\n | \"directory-sync\";\n\ntype AuthToken = string | (() => Promise<string>);\n\ninterface ElevatedAccess {\n token: string;\n expiresAt: string;\n}\n\ninterface ApiContextValue {\n authToken: string | undefined;\n permissions: string[] | undefined;\n exp: number | undefined;\n baseUrl: string;\n widgetType: WidgetType;\n elevatedAccess?: ElevatedAccess;\n setElevatedAccess: (elevatedAccess?: ElevatedAccess) => void;\n /**\n * The token now belongs to a different session, user or organization, but\n * the Client GraphQL data on screen may still belong to the previous one.\n */\n isIdentityPending: boolean;\n}\n\ninterface AuthTokenQueryData {\n authToken: string;\n permissions: string[] | undefined;\n exp: number | undefined;\n}\n\nconst authTokenQueryKey = (widgetType: WidgetType) =>\n [\"authToken\", widgetType] as const;\n\n// Refetch this many ms before the JWT expires so data queries always see a\n// fresh token.\nconst TOKEN_REFRESH_BUFFER_MS = 30_000;\n\n// Cap setTimeout delays at ~24 days to stay well below the 32-bit signed int\n// limit (~24.8 days), which some runtimes silently coerce to 1ms.\nconst MAX_TIMEOUT_MS = 2_147_483_000;\n\nconst ApiContext = React.createContext<ApiContextValue | undefined>(undefined);\nApiContext.displayName = \"ApiContext\";\n\ninterface ApiProviderProps {\n authToken: AuthToken;\n baseUrl: string;\n children: React.ReactNode;\n widgetType: WidgetType;\n}\n\nconst ApiProvider: React.FC<ApiProviderProps> = ({\n authToken,\n baseUrl,\n children,\n widgetType,\n}) => {\n const queryClient = useQueryClient();\n const queryKey = authTokenQueryKey(widgetType);\n const authTokenQuery = useQuery<AuthTokenQueryData>({\n initialData: () => {\n if (typeof authToken !== \"string\") {\n return undefined;\n }\n\n const claims = getClaims(authToken);\n return {\n authToken,\n permissions: claims.permissions,\n exp: claims.exp,\n };\n },\n queryFn: async () => {\n const resolvedToken =\n typeof authToken === \"string\" ? authToken : await authToken();\n const claims = getClaims(resolvedToken);\n\n return {\n authToken: resolvedToken,\n permissions: claims.permissions,\n exp: claims.exp,\n };\n },\n queryKey,\n });\n\n // `initialData` only seeds the cache on first mount, so a new string token\n // has to be written to the cache explicitly.\n React.useEffect(() => {\n if (typeof authToken !== \"string\") {\n return;\n }\n\n const cached = queryClient.getQueryData<AuthTokenQueryData>(queryKey);\n if (cached?.authToken === authToken) {\n return;\n }\n\n // Keep an in-flight fetch holding the previous token from overwriting the\n // new one.\n void queryClient.cancelQueries({ queryKey });\n const claims = getClaims(authToken);\n queryClient.setQueryData<AuthTokenQueryData>(queryKey, {\n authToken,\n permissions: claims.permissions,\n exp: claims.exp,\n });\n }, [authToken, queryClient, queryKey]);\n\n const currentToken =\n typeof authToken === \"string\" ? authToken : authTokenQuery.data?.authToken;\n const currentIdentityKey = React.useMemo(\n () => (currentToken ? getIdentityKey(getClaims(currentToken)) : undefined),\n [currentToken],\n );\n // Counts changes to the token's session, user or organization (e.g. after\n // switching organizations). Switching back to an earlier identity counts too,\n // since requests made for the one in between may still be in flight.\n const [tokenIdentity, setTokenIdentity] = React.useState({\n key: currentIdentityKey,\n generation: 0,\n });\n if (currentIdentityKey !== tokenIdentity.key) {\n setTokenIdentity({\n key: currentIdentityKey,\n generation:\n tokenIdentity.key === undefined\n ? tokenIdentity.generation\n : tokenIdentity.generation + 1,\n });\n }\n // The identity generation whose Client GraphQL data is on screen. The\n // previous identity's data stays on screen until it has been refetched with\n // the new token.\n const [settledGeneration, setSettledGeneration] = React.useState(0);\n const identityGeneration = tokenIdentity.generation;\n const isIdentityPending = settledGeneration !== identityGeneration;\n React.useEffect(() => {\n if (!isIdentityPending) {\n return;\n }\n\n // Cancels refetches still in flight for an earlier identity.\n void queryClient.invalidateQueries({ predicate: isClientGraphqlQuery });\n // A failed refetch leaves its query invalidated, so the previous identity's\n // data stays locked until a refetch succeeds.\n const settleWhenRefetched = () => {\n const isRefetching = queryClient\n .getQueryCache()\n .findAll({ predicate: isClientGraphqlQuery })\n .some(\n (query) =>\n query.state.fetchStatus === \"fetching\" ||\n (query.isActive() && query.state.isInvalidated),\n );\n if (!isRefetching) {\n setSettledGeneration(identityGeneration);\n }\n };\n const unsubscribe = queryClient\n .getQueryCache()\n .subscribe(settleWhenRefetched);\n settleWhenRefetched();\n return unsubscribe;\n }, [identityGeneration, isIdentityPending, queryClient]);\n\n // Keep the token \"fresh\" until ~30s before it expires, so window-focus\n // re-fetches re-mint before data queries use a stale Bearer.\n //\n // TODO(v2): prefer the function-form `staleTime` on `useQuery` above once we\n // can bump the `@tanstack/react-query` peer dep to ^5.44.0 (the version that\n // introduced it). That lets react-query own the freshness window and avoids\n // the extra timer + window-focus nonsense this effect introduces.\n const exp = authTokenQuery.data?.exp;\n React.useEffect(() => {\n if (!exp) {\n return;\n }\n\n const delay = Math.min(\n MAX_TIMEOUT_MS,\n exp * 1000 - Date.now() - TOKEN_REFRESH_BUFFER_MS,\n );\n if (delay <= 0) {\n void queryClient.refetchQueries({\n queryKey: authTokenQueryKey(widgetType),\n });\n return;\n }\n const timeout = window.setTimeout(() => {\n void queryClient.refetchQueries({\n queryKey: authTokenQueryKey(widgetType),\n });\n }, delay);\n return () => window.clearTimeout(timeout);\n }, [exp, queryClient, widgetType]);\n\n const [elevatedAccess, setElevatedAccess] = React.useState<ElevatedAccess>();\n const elevatedAccessTimeout = React.useRef<number | undefined>(undefined);\n\n // This effect manages the expiration of elevated access tokens\n // When an elevated access token is present, it checks every 30 seconds if the token has expired\n // If the token has expired (current time > expiration time), it clears the elevated access\n React.useEffect(() => {\n if (elevatedAccessTimeout.current) {\n window.clearInterval(elevatedAccessTimeout.current);\n }\n\n if (elevatedAccess) {\n elevatedAccessTimeout.current = window.setInterval(() => {\n const now = new Date();\n const expiresAtDate = new Date(elevatedAccess.expiresAt);\n\n // Reset the elevated access if it has expired\n if (now > expiresAtDate) {\n setElevatedAccess(undefined);\n }\n }, 30_000); // every 30 seconds\n }\n\n return () => {\n if (elevatedAccessTimeout.current) {\n window.clearInterval(elevatedAccessTimeout.current);\n }\n };\n }, [elevatedAccess]);\n\n const value = React.useMemo(\n () => ({\n authToken: authTokenQuery.data?.authToken,\n permissions: authTokenQuery.data?.permissions,\n exp: authTokenQuery.data?.exp,\n baseUrl,\n elevatedAccess,\n setElevatedAccess,\n widgetType,\n isIdentityPending,\n }),\n [\n authTokenQuery.data,\n baseUrl,\n elevatedAccess,\n setElevatedAccess,\n widgetType,\n isIdentityPending,\n ],\n );\n\n return <ApiContext.Provider value={value}>{children}</ApiContext.Provider>;\n};\n\nfunction isClientGraphqlQuery(query: Query) {\n return isClientGraphqlQueryKey(query.queryKey);\n}\n\nfunction getIdentityKey(claims: Claims) {\n return JSON.stringify([claims.sid, claims.sub, claims.org_id]);\n}\n\nconst useApi = () => {\n const context = React.useContext(ApiContext);\n\n if (context === undefined) {\n throw new Error(\"useApi must be used within an ApiProvider\");\n }\n\n return context;\n};\n\nconst useElevatedAccessToken = () => {\n const { elevatedAccess, setElevatedAccess } = useApi();\n\n return { elevatedAccess, setElevatedAccess };\n};\n\nconst useApiReady = () => {\n const { authToken } = useApi();\n const isHydrated = useIsHydrated();\n\n return isHydrated && authToken !== undefined;\n};\nexport type { WidgetType, AuthToken, AuthTokenQueryData };\nexport {\n authTokenQueryKey,\n ApiProvider,\n useApi,\n useElevatedAccessToken,\n useApiReady,\n};\n"],"mappings":"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"}
@@ -14,6 +14,11 @@ interface ApiContextValue {
14
14
  widgetType: WidgetType;
15
15
  elevatedAccess?: ElevatedAccess;
16
16
  setElevatedAccess: (elevatedAccess?: ElevatedAccess) => void;
17
+ /**
18
+ * The token now belongs to a different session, user or organization, but
19
+ * the Client GraphQL data on screen may still belong to the previous one.
20
+ */
21
+ isIdentityPending: boolean;
17
22
  }
18
23
  interface AuthTokenQueryData {
19
24
  authToken: string;
@@ -1 +1 @@
1
- {"version":3,"file":"api-provider.d.cts","names":[],"sources":["../../../src/api/api-provider.tsx"],"mappings":";;KAOK;KAeA,4BAA4B;UAEvB;EACR;EACA;;UAGQ;EACR;EACA;EACA;EACA;EACA,YAAY;EACZ,iBAAiB;EACjB,oBAAoB,iBAAiB;;UAG7B;EACR;EACA;EACA;;cAGI,oBAAiB,YAAgB,sCACzB;UAaJ;EACR,WAAW;EACX;EACA,UAAU,MAAM;EAChB,YAAY;;cAGR,aAAa,MAAM,GAAG;cAyJtB,cAAM;cAUN;kBAAA;EAjMiC,oBAAA,iBAAA;;cAuMjC"}
1
+ {"version":3,"file":"api-provider.d.cts","names":[],"sources":["../../../src/api/api-provider.tsx"],"mappings":";;KAQK;KAeA,4BAA4B;UAEvB;EACR;EACA;;UAGQ;EACR;EACA;EACA;EACA;EACA,YAAY;EACZ,iBAAiB;EACjB,oBAAoB,iBAAiB;;;;;EAKrC;;UAGQ;EACR;EACA;EACA;;cAGI,oBAAiB,YAAgB,sCACzB;UAaJ;EACR,WAAW;EACX;EACA,UAAU,MAAM;EAChB,YAAY;;cAGR,aAAa,MAAM,GAAG;cAgNtB,cAAM;cAUN;kBAAA;EA7PiC,oBAAA,iBAAA;;cAmQjC"}