@hanzo/rooms 0.1.54 → 0.1.56
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/dist/Orgs.js +2 -3
- package/dist/lib/host.d.ts +11 -4
- package/dist/lib/host.js +18 -6
- package/dist/lib/signout.d.ts +1 -1
- package/dist/lib/signout.js +3 -11
- package/dist/lib/tier.d.ts +48 -1
- package/dist/lib/tier.js +52 -17
- package/package.json +1 -1
package/dist/Orgs.js
CHANGED
|
@@ -297,9 +297,8 @@ function Veil() {
|
|
|
297
297
|
/**
|
|
298
298
|
* The room, in the one place every branch that draws it puts it. Under the veil
|
|
299
299
|
* it is inert and hidden from assistive technology, so what the veil covers
|
|
300
|
-
* takes no focus, key or press:
|
|
301
|
-
*
|
|
302
|
-
* lays out as its children do.
|
|
300
|
+
* takes no focus, key or press: a team on Free cannot type and send before its
|
|
301
|
+
* ask is drawn. It lays out as its children do.
|
|
303
302
|
*/
|
|
304
303
|
function Held({ veiled, children }) {
|
|
305
304
|
return (_jsx(View, { render: _jsx("div", { "data-slot": "held", inert: veiled, "aria-hidden": veiled || undefined }), display: "contents", children: children }));
|
package/dist/lib/host.d.ts
CHANGED
|
@@ -130,9 +130,16 @@ export declare const WEB = "https://hanzo.ai/chat";
|
|
|
130
130
|
export declare const guest: (hostname: string) => boolean;
|
|
131
131
|
/**
|
|
132
132
|
* Whether a host is the devnet's: its production twin with `-dev` on the one
|
|
133
|
-
* label under hanzo.ai (app-dev.hanzo.ai, api-dev.hanzo.ai, id-dev.hanzo.ai)
|
|
134
|
-
*
|
|
135
|
-
* (
|
|
136
|
-
*
|
|
133
|
+
* label under hanzo.ai (app-dev.hanzo.ai, api-dev.hanzo.ai, id-dev.hanzo.ai). A
|
|
134
|
+
* deeper name (a.b-dev.hanzo.ai) is not one, and neither is a name that only
|
|
135
|
+
* contains the suffix (app-dev.hanzo.ai.example.com). dev.hanzo.ai is not one
|
|
136
|
+
* either: it is hanzo.ai/dev's old address and redirects there.
|
|
137
137
|
*/
|
|
138
138
|
export declare function devnet(hostname: string): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Whether a host answers IAM's sign-in and sign-out calls on its own origin, so
|
|
141
|
+
* a page there signs in without leaving it. Every devnet host does: app-dev's
|
|
142
|
+
* /v1 is the devnet cloud, whose IAM is the devnet's own (universe hanzo-devnet
|
|
143
|
+
* cloud.yaml).
|
|
144
|
+
*/
|
|
145
|
+
export declare const answers: (hostname: string) => boolean;
|
package/dist/lib/host.js
CHANGED
|
@@ -201,16 +201,28 @@ export const guest = (hostname) => hostname === 'hanzo.app' || hostname.endsWith
|
|
|
201
201
|
const DEVNET = '-dev.hanzo.ai';
|
|
202
202
|
/**
|
|
203
203
|
* Whether a host is the devnet's: its production twin with `-dev` on the one
|
|
204
|
-
* label under hanzo.ai (app-dev.hanzo.ai, api-dev.hanzo.ai, id-dev.hanzo.ai)
|
|
205
|
-
*
|
|
206
|
-
* (
|
|
207
|
-
*
|
|
204
|
+
* label under hanzo.ai (app-dev.hanzo.ai, api-dev.hanzo.ai, id-dev.hanzo.ai). A
|
|
205
|
+
* deeper name (a.b-dev.hanzo.ai) is not one, and neither is a name that only
|
|
206
|
+
* contains the suffix (app-dev.hanzo.ai.example.com). dev.hanzo.ai is not one
|
|
207
|
+
* either: it is hanzo.ai/dev's old address and redirects there.
|
|
208
208
|
*/
|
|
209
209
|
export function devnet(hostname) {
|
|
210
|
-
if (hostname === 'dev.hanzo.ai')
|
|
211
|
-
return true;
|
|
212
210
|
if (!hostname.endsWith(DEVNET))
|
|
213
211
|
return false;
|
|
214
212
|
const label = hostname.slice(0, -'.hanzo.ai'.length);
|
|
215
213
|
return label.length > '-dev'.length && !label.includes('.');
|
|
216
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* The hosts that answer IAM's sign-in and sign-out calls on their own origin:
|
|
217
|
+
* every host this export is served on and signs in at (universe static-sites
|
|
218
|
+
* `iam-on-hanzo-ai` and cloud.yaml `iam-signin`), testnet's test.hanzo.ai, and
|
|
219
|
+
* the dev server's /v1 proxy.
|
|
220
|
+
*/
|
|
221
|
+
const ANSWERS = new Set([...APEX, 'cloud.hanzo.ai', 'hanzo.bot', 'bot.hanzo.ai', 'test.hanzo.ai', 'localhost', '127.0.0.1']);
|
|
222
|
+
/**
|
|
223
|
+
* Whether a host answers IAM's sign-in and sign-out calls on its own origin, so
|
|
224
|
+
* a page there signs in without leaving it. Every devnet host does: app-dev's
|
|
225
|
+
* /v1 is the devnet cloud, whose IAM is the devnet's own (universe hanzo-devnet
|
|
226
|
+
* cloud.yaml).
|
|
227
|
+
*/
|
|
228
|
+
export const answers = (hostname) => ANSWERS.has(hostname) || devnet(hostname);
|
package/dist/lib/signout.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IAM } from '@hanzo/iam';
|
|
2
|
-
/** Whether the page in front of us is on
|
|
2
|
+
/** Whether the page in front of us is on an origin that answers IAM itself (`answers` in host.ts). */
|
|
3
3
|
export declare const here: () => boolean;
|
|
4
4
|
/** The IAM client for this page; the token exchange at the callback shares its PKCE store. */
|
|
5
5
|
export declare function useClient(): IAM;
|
package/dist/lib/signout.js
CHANGED
|
@@ -12,17 +12,9 @@ import { IAM } from '@hanzo/iam';
|
|
|
12
12
|
import { useIam } from '@hanzo/iam/react';
|
|
13
13
|
import { useSignOut as useUiSignOut } from '@hanzo/ui/auth';
|
|
14
14
|
import { track } from './tags.js';
|
|
15
|
-
import {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
* every host this export is served on and signs in at (universe static-sites
|
|
19
|
-
* `iam-on-hanzo-ai` and cloud.yaml `iam-signin`), each network's own
|
|
20
|
-
* (test.hanzo.ai for testnet, dev.hanzo.ai for devnet), and the dev server's
|
|
21
|
-
* /v1 proxy.
|
|
22
|
-
*/
|
|
23
|
-
const HOSTS = new Set([...APEX, 'cloud.hanzo.ai', 'hanzo.bot', 'bot.hanzo.ai', 'test.hanzo.ai', 'dev.hanzo.ai', 'localhost', '127.0.0.1']);
|
|
24
|
-
/** Whether the page in front of us is on such an origin. */
|
|
25
|
-
export const here = () => typeof window !== 'undefined' && HOSTS.has(window.location.hostname);
|
|
15
|
+
import { answers } from './host.js';
|
|
16
|
+
/** Whether the page in front of us is on an origin that answers IAM itself (`answers` in host.ts). */
|
|
17
|
+
export const here = () => typeof window !== 'undefined' && answers(window.location.hostname);
|
|
26
18
|
/** The IAM client for this page; the token exchange at the callback shares its PKCE store. */
|
|
27
19
|
export function useClient() {
|
|
28
20
|
const { config } = useIam();
|
package/dist/lib/tier.d.ts
CHANGED
|
@@ -93,10 +93,57 @@ export declare function useSubscription(enabled?: boolean, org?: string | null):
|
|
|
93
93
|
* are asked.
|
|
94
94
|
*/
|
|
95
95
|
export declare function paying(signal?: AbortSignal): Promise<boolean>;
|
|
96
|
+
/** The last tier an organization was read at. */
|
|
97
|
+
export type Read = {
|
|
98
|
+
org: string | null;
|
|
99
|
+
tier: Tier;
|
|
100
|
+
};
|
|
101
|
+
/** What a read left held, and for which organization. */
|
|
102
|
+
export type Held = {
|
|
103
|
+
org: string | null;
|
|
104
|
+
tier: Tier | null;
|
|
105
|
+
wrong: unknown;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Whether a failed read may answer otherwise on the next try: no answer at all
|
|
109
|
+
* (the network, a timeout), too many asks (429) or billing's own failure (a
|
|
110
|
+
* 5xx). A 401, 403 or 404 is billing's answer, and stands.
|
|
111
|
+
*/
|
|
112
|
+
export declare function transient(e: unknown): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* What a read of the tier leaves held, and the tier the next read falls back
|
|
115
|
+
* on. A read that answers is the tier. One that fails for a moment
|
|
116
|
+
* (`transient`) keeps the tier this organization was last read at, so a reader
|
|
117
|
+
* is not swapped out of a room they pay for by a re-read that did not answer
|
|
118
|
+
* (the gate reads again on focus and on return). Billing's refusal, or a
|
|
119
|
+
* failure with nothing read for it yet, holds nothing and says why, which keeps
|
|
120
|
+
* every gate shut; a refusal also forgets the tier read before it, so no later
|
|
121
|
+
* failure brings it back.
|
|
122
|
+
*/
|
|
123
|
+
export declare function hold(last: Read | null, org: string | null, read: {
|
|
124
|
+
tier: Tier;
|
|
125
|
+
} | {
|
|
126
|
+
wrong: unknown;
|
|
127
|
+
}): {
|
|
128
|
+
tier: Tier | null;
|
|
129
|
+
wrong: unknown;
|
|
130
|
+
last: Read | null;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* The tier and the refusal held for `org`, and nothing for any other: what was
|
|
134
|
+
* read for the organization a reader just left is never the one they are in,
|
|
135
|
+
* so a switch from a paid organization to a team on Free shows no room while
|
|
136
|
+
* the new one is read.
|
|
137
|
+
*/
|
|
138
|
+
export declare function standing(held: Held | null, org: string | null): {
|
|
139
|
+
tier: Tier | null;
|
|
140
|
+
wrong: unknown;
|
|
141
|
+
};
|
|
96
142
|
/**
|
|
97
143
|
* The tier, held. `null` while unknown — which is BOTH "still asking" and
|
|
98
144
|
* "asked and was refused", so `wrong` is kept beside it rather than swallowed:
|
|
99
|
-
* a row cannot choose between saying nothing and saying why without it.
|
|
145
|
+
* a row cannot choose between saying nothing and saying why without it. A
|
|
146
|
+
* re-read that fails keeps the last tier read (`hold`).
|
|
100
147
|
*/
|
|
101
148
|
export declare function useTier(enabled?: boolean, org?: string | null): {
|
|
102
149
|
tier: Tier | null;
|
package/dist/lib/tier.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* reads first, so a refusal here already speaks the vocabulary every room uses.
|
|
22
22
|
* A second Refusal class would be a second vocabulary for the same event.
|
|
23
23
|
*/
|
|
24
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
24
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
25
25
|
import { api } from './api.js';
|
|
26
26
|
import { bearer, org, orgs, scope } from './session.js';
|
|
27
27
|
import { list } from './list.js';
|
|
@@ -147,34 +147,69 @@ export async function paying(signal) {
|
|
|
147
147
|
}));
|
|
148
148
|
return reads.some((r) => r.status === 'fulfilled' && paid(r.value));
|
|
149
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Whether a failed read may answer otherwise on the next try: no answer at all
|
|
152
|
+
* (the network, a timeout), too many asks (429) or billing's own failure (a
|
|
153
|
+
* 5xx). A 401, 403 or 404 is billing's answer, and stands.
|
|
154
|
+
*/
|
|
155
|
+
export function transient(e) {
|
|
156
|
+
const status = e?.status;
|
|
157
|
+
return typeof status !== 'number' || status === 408 || status === 429 || status >= 500;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* What a read of the tier leaves held, and the tier the next read falls back
|
|
161
|
+
* on. A read that answers is the tier. One that fails for a moment
|
|
162
|
+
* (`transient`) keeps the tier this organization was last read at, so a reader
|
|
163
|
+
* is not swapped out of a room they pay for by a re-read that did not answer
|
|
164
|
+
* (the gate reads again on focus and on return). Billing's refusal, or a
|
|
165
|
+
* failure with nothing read for it yet, holds nothing and says why, which keeps
|
|
166
|
+
* every gate shut; a refusal also forgets the tier read before it, so no later
|
|
167
|
+
* failure brings it back.
|
|
168
|
+
*/
|
|
169
|
+
export function hold(last, org, read) {
|
|
170
|
+
if ('tier' in read)
|
|
171
|
+
return { tier: read.tier, wrong: null, last: { org, tier: read.tier } };
|
|
172
|
+
const mine = last && last.org === org ? last : null;
|
|
173
|
+
if (transient(read.wrong) && mine)
|
|
174
|
+
return { tier: mine.tier, wrong: null, last: mine };
|
|
175
|
+
return { tier: null, wrong: read.wrong, last: transient(read.wrong) ? last : null };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The tier and the refusal held for `org`, and nothing for any other: what was
|
|
179
|
+
* read for the organization a reader just left is never the one they are in,
|
|
180
|
+
* so a switch from a paid organization to a team on Free shows no room while
|
|
181
|
+
* the new one is read.
|
|
182
|
+
*/
|
|
183
|
+
export function standing(held, org) {
|
|
184
|
+
return held && held.org === org ? { tier: held.tier, wrong: held.wrong } : { tier: null, wrong: null };
|
|
185
|
+
}
|
|
150
186
|
/**
|
|
151
187
|
* The tier, held. `null` while unknown — which is BOTH "still asking" and
|
|
152
188
|
* "asked and was refused", so `wrong` is kept beside it rather than swallowed:
|
|
153
|
-
* a row cannot choose between saying nothing and saying why without it.
|
|
189
|
+
* a row cannot choose between saying nothing and saying why without it. A
|
|
190
|
+
* re-read that fails keeps the last tier read (`hold`).
|
|
154
191
|
*/
|
|
155
192
|
export function useTier(enabled = true, org = null) {
|
|
156
|
-
const [
|
|
157
|
-
const [wrong, setWrong] = useState(null);
|
|
193
|
+
const [held, setHeld] = useState(null);
|
|
158
194
|
const [nonce, setNonce] = useState(0);
|
|
195
|
+
const last = useRef(null);
|
|
159
196
|
useEffect(() => {
|
|
160
197
|
if (!enabled)
|
|
161
198
|
return;
|
|
162
199
|
const stop = new AbortController();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
});
|
|
200
|
+
// Asking again: the refusal the last read gave is no longer the answer.
|
|
201
|
+
setHeld((h) => (h && h.org === org ? { ...h, wrong: null } : h));
|
|
202
|
+
const settle = (read) => {
|
|
203
|
+
if (stop.signal.aborted)
|
|
204
|
+
return;
|
|
205
|
+
const next = hold(last.current, org, read);
|
|
206
|
+
last.current = next.last;
|
|
207
|
+
setHeld({ org, tier: next.tier, wrong: next.wrong });
|
|
208
|
+
};
|
|
209
|
+
readTier(stop.signal).then((got) => settle({ tier: got }), (e) => settle({ wrong: e }));
|
|
175
210
|
return () => stop.abort();
|
|
176
211
|
}, [enabled, nonce, org]);
|
|
177
|
-
return {
|
|
212
|
+
return { ...standing(held, org), reload: useCallback(() => setNonce((n) => n + 1), []) };
|
|
178
213
|
}
|
|
179
214
|
/**
|
|
180
215
|
* Whether an organization is its owner's personal one, off IAM's own row.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/rooms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|