@cosmoops/connect-sdk 0.32.0 → 0.34.0
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 +251 -0
- package/README.md +17 -5
- package/dist/cjs/client/components/access-gate.js +98 -2
- package/dist/cjs/client/components/visitor-chat.js +387 -0
- package/dist/cjs/client/components/visitor-stream.js +124 -0
- package/dist/cjs/client/index.js +16 -3
- package/dist/cjs/client/provider.js +29 -2
- package/dist/cjs/client/use-checkout.js +3 -1
- package/dist/cjs/helpers.js +46 -0
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/next/index.js +10 -3
- package/dist/cjs/server/client.js +8 -1
- package/dist/cjs/server/handler.js +142 -0
- package/dist/cjs/server/resources/ai.js +50 -0
- package/dist/cjs/server/resources/billing.js +5 -0
- package/dist/cjs/server/resources/enrichment.js +59 -0
- package/dist/cjs/server/resources/entitlements.js +47 -0
- package/dist/cjs/server/resources/visitor.js +227 -0
- package/dist/esm/client/components/access-gate.js +99 -3
- package/dist/esm/client/components/visitor-chat.js +384 -0
- package/dist/esm/client/components/visitor-stream.js +120 -0
- package/dist/esm/client/index.js +11 -1
- package/dist/esm/client/provider.js +28 -2
- package/dist/esm/client/use-checkout.js +3 -1
- package/dist/esm/helpers.js +43 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/next/index.js +10 -3
- package/dist/esm/server/client.js +8 -1
- package/dist/esm/server/handler.js +142 -0
- package/dist/esm/server/resources/ai.js +50 -0
- package/dist/esm/server/resources/billing.js +5 -0
- package/dist/esm/server/resources/enrichment.js +59 -0
- package/dist/esm/server/resources/entitlements.js +47 -0
- package/dist/esm/server/resources/visitor.js +222 -0
- package/dist/types/client/components/access-gate.d.ts +9 -0
- package/dist/types/client/components/visitor-chat.d.ts +120 -0
- package/dist/types/client/components/visitor-stream.d.ts +42 -0
- package/dist/types/client/index.d.ts +6 -1
- package/dist/types/client/provider.d.ts +176 -51
- package/dist/types/client/use-checkout.d.ts +18 -1
- package/dist/types/helpers.d.ts +36 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/server/client.d.ts +7 -1
- package/dist/types/server/handler.d.ts +0 -24
- package/dist/types/server/resources/ai.d.ts +48 -1
- package/dist/types/server/resources/billing.d.ts +4 -0
- package/dist/types/server/resources/enrichment.d.ts +57 -1
- package/dist/types/server/resources/entitlements.d.ts +40 -1
- package/dist/types/server/resources/visitor.d.ts +200 -0
- package/dist/types/types.d.ts +562 -0
- package/package.json +3 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export interface ConnectVisitorChatProps {
|
|
2
|
+
/**
|
|
3
|
+
* The customer whose website this widget is on.
|
|
4
|
+
*
|
|
5
|
+
* Required, and it is the one field that decides whose published knowledge the
|
|
6
|
+
* assistant answers from and whose diary it books into. Your bridge checks it
|
|
7
|
+
* against your own subscriptions before it reaches Connect.
|
|
8
|
+
*/
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
/**
|
|
11
|
+
* What the assistant calls itself, and the copy shown before anybody types.
|
|
12
|
+
*
|
|
13
|
+
* **Yours, not Connect's.** These never reach the model and are rendered from
|
|
14
|
+
* your own store, which is what lets the bubble paint its first frame with no
|
|
15
|
+
* network call at all. Connect holds what it enforces; you hold what you draw.
|
|
16
|
+
*/
|
|
17
|
+
assistantName?: string;
|
|
18
|
+
greeting?: string;
|
|
19
|
+
/** Opening questions offered as buttons. Three is what fits above a phone keyboard. */
|
|
20
|
+
suggestions?: readonly string[];
|
|
21
|
+
/** A published event type slug in that workspace, to offer a call. Omit for no booking. */
|
|
22
|
+
bookingEventTypeSlug?: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Where `createConnectHandler` is mounted. Defaults to `/api/connect`, which
|
|
25
|
+
* is its own default.
|
|
26
|
+
*
|
|
27
|
+
* Passed explicitly here, rather than only inherited from `ConnectProvider`,
|
|
28
|
+
* because this component is built to work on a page that has no provider -
|
|
29
|
+
* see the component's own note.
|
|
30
|
+
*/
|
|
31
|
+
basePath?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The page the visitor is on, passed to Connect as context.
|
|
34
|
+
*
|
|
35
|
+
* Left unset it uses `location.pathname`. Pass something more meaningful - a
|
|
36
|
+
* page title, a product slug - where the path is not descriptive.
|
|
37
|
+
*/
|
|
38
|
+
page?: string;
|
|
39
|
+
/** The bubble's accessible label, and the panel's heading. */
|
|
40
|
+
title?: string;
|
|
41
|
+
/** Start open. The panel owns its state after that. */
|
|
42
|
+
defaultOpen?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Show the bubble even to a signed-in user. Off by default.
|
|
45
|
+
*
|
|
46
|
+
* The default is the useful one: a signed-in member has your product's own
|
|
47
|
+
* support surfaces and, if you mount it, `<ConnectAskAi>` - which is grounded
|
|
48
|
+
* in their actual workspace and can therefore answer things this assistant
|
|
49
|
+
* cannot. Two bubbles in the same corner is also the one layout bug this
|
|
50
|
+
* component could cause, since `<ConnectAskAi>` sits at the same `z-30`
|
|
51
|
+
* bottom-right position.
|
|
52
|
+
*
|
|
53
|
+
* Turn it on for a marketing site that is a separate surface from the
|
|
54
|
+
* application, where "signed in" says nothing about what the visitor is
|
|
55
|
+
* reading.
|
|
56
|
+
*/
|
|
57
|
+
showWhenSignedIn?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Replaces `fetch`, for a mount with **no `ConnectProvider`** around it.
|
|
60
|
+
*
|
|
61
|
+
* Inside a provider this is not needed and not read: the component goes
|
|
62
|
+
* through `mutateResource`/`streamResource`, which already use whatever
|
|
63
|
+
* `fetch` that provider was given - so Storybook and the docs site work by
|
|
64
|
+
* wrapping it, with nothing passed here.
|
|
65
|
+
*/
|
|
66
|
+
fetch?: (input: string, init?: RequestInit) => Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The public chat bubble: a visitor with no account asks about your product,
|
|
70
|
+
* books a call, or gets started.
|
|
71
|
+
*
|
|
72
|
+
* ## It runs on pages that have no `ConnectProvider`
|
|
73
|
+
*
|
|
74
|
+
* Deliberately, and it is the main thing separating this component from every
|
|
75
|
+
* other one here. A marketing page usually has no session to hold and nothing
|
|
76
|
+
* on it that needs one, so requiring the provider would mean wrapping a
|
|
77
|
+
* landing page in an auth context to put a chat bubble on it. It reads the
|
|
78
|
+
* context through `useConnectOptional` and uses it only for `basePath` and the
|
|
79
|
+
* injected `fetch`; both have props, and both have defaults.
|
|
80
|
+
*
|
|
81
|
+
* ## Nothing happens until somebody opens it
|
|
82
|
+
*
|
|
83
|
+
* The conversation is minted on first open, never on mount. On a busy marketing
|
|
84
|
+
* site mounting would mean one Firestore write and one wasted session row per
|
|
85
|
+
* *page view*, for a bubble most visitors never touch - and the bubble itself
|
|
86
|
+
* needs no server state to render. The greeting arrives with that first call
|
|
87
|
+
* and is stored copy rather than generated, so opening costs one fast round
|
|
88
|
+
* trip and the visitor is reading real words immediately.
|
|
89
|
+
*
|
|
90
|
+
* ## It is built for a phone first
|
|
91
|
+
*
|
|
92
|
+
* Open, it is a full-height sheet on a narrow viewport and an anchored panel
|
|
93
|
+
* from `sm` up - a marketing chat that covers a desktop browser is harder to
|
|
94
|
+
* use, and one that does not cover a phone has nowhere to put a keyboard.
|
|
95
|
+
* `h-dvh` rather than `h-screen`, so the sheet tracks the visual viewport when
|
|
96
|
+
* the keyboard opens; `env(safe-area-inset-bottom)` on the composer, so the
|
|
97
|
+
* send button is not under a home indicator.
|
|
98
|
+
*
|
|
99
|
+
* The composer's input type is **the server's decision**, not a guess from what
|
|
100
|
+
* the assistant said - see `VisitorField`. That is what puts an `@` on the
|
|
101
|
+
* keyboard when an email is wanted and lets iOS offer a verification code
|
|
102
|
+
* straight from the notification.
|
|
103
|
+
*
|
|
104
|
+
* ## The times are drawn from the tool result, never the prose
|
|
105
|
+
*
|
|
106
|
+
* A turn that looked up availability carries the slots as structured data, and
|
|
107
|
+
* the buttons come from that. So a time the visitor taps is one the server
|
|
108
|
+
* offered, which is the property `/visitor/book` relies on and cannot check for
|
|
109
|
+
* itself.
|
|
110
|
+
*
|
|
111
|
+
* ## Replies render as text, not markdown
|
|
112
|
+
*
|
|
113
|
+
* `<ConnectAskAi>` lazy-loads a markdown renderer because it answers questions
|
|
114
|
+
* about a workspace's documents, where tables and code blocks carry meaning.
|
|
115
|
+
* This answers three-sentence questions about a product on a marketing page.
|
|
116
|
+
* Shipping 462 KB of renderer to a landing page for prose that is deliberately
|
|
117
|
+
* kept to a couple of lines is the wrong trade, so line breaks are preserved
|
|
118
|
+
* and nothing else is parsed.
|
|
119
|
+
*/
|
|
120
|
+
export declare function ConnectVisitorChat({ workspaceId, assistantName, greeting, suggestions, bookingEventTypeSlug, basePath, page, title, defaultOpen, showWhenSignedIn, fetch: fetchProp, }: ConnectVisitorChatProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { VisitorSlotOffer, VisitorToolCall, VisitorTurnDone } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The visitor turn's wire format, read.
|
|
4
|
+
*
|
|
5
|
+
* **A named-event reader, where `<ConnectAskAi>`'s is data-only.** That one
|
|
6
|
+
* consumes the AI SDK's UI message stream, where every frame is a `data:` line
|
|
7
|
+
* carrying a `type` field. This endpoint answers `event:`/`data:` pairs instead,
|
|
8
|
+
* for the reason the route explains: the UI message protocol exists for
|
|
9
|
+
* `useChat`, which owns the message list client-side, and this surface
|
|
10
|
+
* deliberately does not let the client own the transcript.
|
|
11
|
+
*
|
|
12
|
+
* Kept out of the component and pure so it can be tested without a DOM. The
|
|
13
|
+
* three behaviours worth pinning are all here: a frame split across two network
|
|
14
|
+
* reads, a malformed frame, and a stream that ends without its `done`.
|
|
15
|
+
*/
|
|
16
|
+
export interface VisitorStreamHandlers {
|
|
17
|
+
onDelta(text: string): void;
|
|
18
|
+
onDone(done: VisitorTurnDone): void;
|
|
19
|
+
/**
|
|
20
|
+
* A failure the server reported *after* the stream opened.
|
|
21
|
+
*
|
|
22
|
+
* It arrives as an event rather than a status because the status was already
|
|
23
|
+
* sent, and the message is written as a sentence for the same reason - it is
|
|
24
|
+
* shown to the visitor verbatim.
|
|
25
|
+
*/
|
|
26
|
+
onError(message: string): void;
|
|
27
|
+
}
|
|
28
|
+
export declare function readVisitorStream(response: Response, handlers: VisitorStreamHandlers): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* The times a turn looked up, lifted out of its tool calls.
|
|
31
|
+
*
|
|
32
|
+
* **This is what the buttons are drawn from, never the assistant's sentence.** A
|
|
33
|
+
* time the visitor taps is then one the server offered, which is what makes it
|
|
34
|
+
* impossible to book an appointment at a time nobody gave - the property
|
|
35
|
+
* `/visitor/book` depends on and cannot check for itself.
|
|
36
|
+
*
|
|
37
|
+
* `null` when the turn looked nothing up, and also when the lookup *refused* -
|
|
38
|
+
* a tool answering `ok: false` is the assistant being told there is nothing
|
|
39
|
+
* available, and it says so in prose. Rendering an empty slot list under that
|
|
40
|
+
* would be a second, emptier way of saying the same thing.
|
|
41
|
+
*/
|
|
42
|
+
export declare function visitorSlotOfferFrom(toolCalls: readonly VisitorToolCall[]): VisitorSlotOffer | null;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* whatever theme the host application platform already configured, and one theme
|
|
11
11
|
* change lands identically in all of them.
|
|
12
12
|
*/
|
|
13
|
-
export { ConnectProvider, useConnect, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectPlatformAccess, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectCompanyProfile, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
|
|
13
|
+
export { ConnectProvider, useConnect, useConnectOptional, useConnectUser, useConnectWorkspaceType, useConnectAudience, useConnectPlatformAccess, useConnectEntitlements, useConnectPlans, useConnectApps, useConnectCompanyProfile, useConnectAddonUsage, useConnectSessions, useConnectIntegrations, useConnectResource, CONNECT_APP_URL, } from "./provider.js";
|
|
14
14
|
export type { ConnectProviderProps, ConnectContextValue, ConnectSession, ConnectOneTapHint, ConnectOneTapFailure, UseConnectResourceOptions, } from "./provider.js";
|
|
15
15
|
export { blockingBlockers, normalizePlanFeatures } from "../helpers.js";
|
|
16
16
|
export type { WirePlanFeature } from "../helpers.js";
|
|
@@ -52,6 +52,11 @@ export { normalizeNav, flattenNav, normalizePath, resolveActivePath, isNavItemAc
|
|
|
52
52
|
export type { ConnectNav, ConnectNavItem, ConnectNavSection, ConnectNavLinkProps, ConnectLinkRenderer, } from "./components/nav.js";
|
|
53
53
|
export type { ConnectScreen } from "../types.js";
|
|
54
54
|
export { matchConnectScreen, isLinkableConnectScreen, } from "./components/ai-screens.js";
|
|
55
|
+
export { ConnectVisitorChat } from "./components/visitor-chat.js";
|
|
56
|
+
export type { ConnectVisitorChatProps } from "./components/visitor-chat.js";
|
|
57
|
+
export { readVisitorStream, visitorSlotOfferFrom } from "./components/visitor-stream.js";
|
|
58
|
+
export type { VisitorStreamHandlers } from "./components/visitor-stream.js";
|
|
59
|
+
export type { VisitorAppointment, VisitorBooking, VisitorField, VisitorMessage, VisitorSession, VisitorSlot, VisitorSlotOffer, VisitorToolCall, VisitorTurnDone, } from "../types.js";
|
|
55
60
|
export { ConnectAppsGrid } from "./components/apps-grid.js";
|
|
56
61
|
export type { ConnectAppsGridProps } from "./components/apps-grid.js";
|
|
57
62
|
export { ConnectAskAi } from "./components/ask-ai.js";
|
|
@@ -196,7 +196,21 @@ export interface ConnectProviderProps {
|
|
|
196
196
|
* not a Connect URL - it is your backend that exchanges the code.
|
|
197
197
|
*/
|
|
198
198
|
redirectUri?: string;
|
|
199
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Override for non-production Connect instances. Defaults to
|
|
201
|
+
* `https://connect.cosmoops.com`.
|
|
202
|
+
*
|
|
203
|
+
* The twin of `CONNECT_APP_URL`, and *not* derived from it - that variable
|
|
204
|
+
* is read on your server and never reaches the browser. This value is where
|
|
205
|
+
* the user menu's Profile, Billing and Invoices destinations open, where
|
|
206
|
+
* `ConnectAppsGrid` sends an unsubscribed application, where checkout
|
|
207
|
+
* opens, and - if you pass `redirectUri` and so build the authorize URL
|
|
208
|
+
* here rather than in your own `/signin` route - where sign-in goes. Point
|
|
209
|
+
* the server somewhere else and leave this alone and the two halves
|
|
210
|
+
* disagree, so pass both or neither. Your root layout is a Server
|
|
211
|
+
* Component, so `appUrl={process.env.CONNECT_APP_URL}` is enough and needs
|
|
212
|
+
* no browser-visible variable.
|
|
213
|
+
*/
|
|
200
214
|
appUrl?: string;
|
|
201
215
|
/**
|
|
202
216
|
* Replace the `fetch` used to reach your bridge. The browser-side twin of
|
|
@@ -245,6 +259,27 @@ export interface ConnectProviderProps {
|
|
|
245
259
|
*/
|
|
246
260
|
export declare function ConnectProvider({ clientId, basePath, redirectUri, appUrl, fetch: fetchImpl, silentSignIn, children, }: ConnectProviderProps): import("react").JSX.Element;
|
|
247
261
|
export declare function useConnect(): ConnectContextValue;
|
|
262
|
+
/**
|
|
263
|
+
* The context when there is one, `null` when there is not.
|
|
264
|
+
*
|
|
265
|
+
* **For a component that must work on a page with no provider**, which is a
|
|
266
|
+
* real case rather than defensive coding: `<ConnectVisitorChat>` goes on a
|
|
267
|
+
* public marketing page, and those pages frequently have no `ConnectProvider`
|
|
268
|
+
* anywhere near them - there is no session to hold and nothing on the page that
|
|
269
|
+
* needs one. `useConnect()` throwing is right for every component that reads a
|
|
270
|
+
* session; it is wrong for one whose whole purpose is the visitor who has none.
|
|
271
|
+
*
|
|
272
|
+
* What inheriting the context buys such a component, when it is inside one, is
|
|
273
|
+
* `basePath` and the injected `fetch` - so the same component renders against
|
|
274
|
+
* Storybook's mock bridge and the docs site's canned fetch without either of
|
|
275
|
+
* them having to pass anything.
|
|
276
|
+
*
|
|
277
|
+
* Do not reach for this to make an ordinary component tolerant of a missing
|
|
278
|
+
* provider. A session-reading component that silently renders a signed-out
|
|
279
|
+
* state because somebody forgot to wrap their app is a bug that looks like a
|
|
280
|
+
* product decision.
|
|
281
|
+
*/
|
|
282
|
+
export declare function useConnectOptional(): ConnectContextValue | null;
|
|
248
283
|
/** The signed-in user, or `null`. Sugar over `useConnect().session`. */
|
|
249
284
|
export declare function useConnectUser(): ConnectUser | null;
|
|
250
285
|
/**
|
|
@@ -306,11 +341,20 @@ export declare function useConnectEntitlements(): {
|
|
|
306
341
|
isLoading: boolean;
|
|
307
342
|
error: string | null;
|
|
308
343
|
/**
|
|
309
|
-
* Forces a fresh read -
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
344
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
345
|
+
* read.
|
|
346
|
+
*
|
|
347
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
348
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
349
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
350
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
351
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
352
|
+
* the same settled promise and reported the same stale answer forever -
|
|
353
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
354
|
+
* whose workspace had subscribed in a different one.
|
|
355
|
+
*
|
|
356
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
357
|
+
* entry that is already gone is a no-op.
|
|
314
358
|
*/
|
|
315
359
|
refetch(): void;
|
|
316
360
|
};
|
|
@@ -326,11 +370,20 @@ export declare function useConnectAddonUsage(): {
|
|
|
326
370
|
isLoading: boolean;
|
|
327
371
|
error: string | null;
|
|
328
372
|
/**
|
|
329
|
-
* Forces a fresh read -
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
373
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
374
|
+
* read.
|
|
375
|
+
*
|
|
376
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
377
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
378
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
379
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
380
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
381
|
+
* the same settled promise and reported the same stale answer forever -
|
|
382
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
383
|
+
* whose workspace had subscribed in a different one.
|
|
384
|
+
*
|
|
385
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
386
|
+
* entry that is already gone is a no-op.
|
|
334
387
|
*/
|
|
335
388
|
refetch(): void;
|
|
336
389
|
};
|
|
@@ -340,11 +393,20 @@ export declare function useConnectPlans(): {
|
|
|
340
393
|
isLoading: boolean;
|
|
341
394
|
error: string | null;
|
|
342
395
|
/**
|
|
343
|
-
* Forces a fresh read -
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
396
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
397
|
+
* read.
|
|
398
|
+
*
|
|
399
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
400
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
401
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
402
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
403
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
404
|
+
* the same settled promise and reported the same stale answer forever -
|
|
405
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
406
|
+
* whose workspace had subscribed in a different one.
|
|
407
|
+
*
|
|
408
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
409
|
+
* entry that is already gone is a no-op.
|
|
348
410
|
*/
|
|
349
411
|
refetch(): void;
|
|
350
412
|
};
|
|
@@ -363,11 +425,20 @@ export declare function useConnectCompanyProfile(): {
|
|
|
363
425
|
isLoading: boolean;
|
|
364
426
|
error: string | null;
|
|
365
427
|
/**
|
|
366
|
-
* Forces a fresh read -
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
428
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
429
|
+
* read.
|
|
430
|
+
*
|
|
431
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
432
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
433
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
434
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
435
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
436
|
+
* the same settled promise and reported the same stale answer forever -
|
|
437
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
438
|
+
* whose workspace had subscribed in a different one.
|
|
439
|
+
*
|
|
440
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
441
|
+
* entry that is already gone is a no-op.
|
|
371
442
|
*/
|
|
372
443
|
refetch(): void;
|
|
373
444
|
};
|
|
@@ -379,11 +450,20 @@ export declare function useConnectApps(): {
|
|
|
379
450
|
isLoading: boolean;
|
|
380
451
|
error: string | null;
|
|
381
452
|
/**
|
|
382
|
-
* Forces a fresh read -
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
453
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
454
|
+
* read.
|
|
455
|
+
*
|
|
456
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
457
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
458
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
459
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
460
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
461
|
+
* the same settled promise and reported the same stale answer forever -
|
|
462
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
463
|
+
* whose workspace had subscribed in a different one.
|
|
464
|
+
*
|
|
465
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
466
|
+
* entry that is already gone is a no-op.
|
|
387
467
|
*/
|
|
388
468
|
refetch(): void;
|
|
389
469
|
};
|
|
@@ -402,11 +482,20 @@ export declare function useConnectWorkspaces(): {
|
|
|
402
482
|
isLoading: boolean;
|
|
403
483
|
error: string | null;
|
|
404
484
|
/**
|
|
405
|
-
* Forces a fresh read -
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
485
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
486
|
+
* read.
|
|
487
|
+
*
|
|
488
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
489
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
490
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
491
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
492
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
493
|
+
* the same settled promise and reported the same stale answer forever -
|
|
494
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
495
|
+
* whose workspace had subscribed in a different one.
|
|
496
|
+
*
|
|
497
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
498
|
+
* entry that is already gone is a no-op.
|
|
410
499
|
*/
|
|
411
500
|
refetch(): void;
|
|
412
501
|
};
|
|
@@ -421,11 +510,20 @@ export declare function useConnectWorkspaceMembers(): {
|
|
|
421
510
|
isLoading: boolean;
|
|
422
511
|
error: string | null;
|
|
423
512
|
/**
|
|
424
|
-
* Forces a fresh read -
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
513
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
514
|
+
* read.
|
|
515
|
+
*
|
|
516
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
517
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
518
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
519
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
520
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
521
|
+
* the same settled promise and reported the same stale answer forever -
|
|
522
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
523
|
+
* whose workspace had subscribed in a different one.
|
|
524
|
+
*
|
|
525
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
526
|
+
* entry that is already gone is a no-op.
|
|
429
527
|
*/
|
|
430
528
|
refetch(): void;
|
|
431
529
|
};
|
|
@@ -435,11 +533,20 @@ export declare function useConnectIntegrations(): {
|
|
|
435
533
|
isLoading: boolean;
|
|
436
534
|
error: string | null;
|
|
437
535
|
/**
|
|
438
|
-
* Forces a fresh read -
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
536
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
537
|
+
* read.
|
|
538
|
+
*
|
|
539
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
540
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
541
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
542
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
543
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
544
|
+
* the same settled promise and reported the same stale answer forever -
|
|
545
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
546
|
+
* whose workspace had subscribed in a different one.
|
|
547
|
+
*
|
|
548
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
549
|
+
* entry that is already gone is a no-op.
|
|
443
550
|
*/
|
|
444
551
|
refetch(): void;
|
|
445
552
|
};
|
|
@@ -455,11 +562,20 @@ export declare function useConnectSessions(): {
|
|
|
455
562
|
isLoading: boolean;
|
|
456
563
|
error: string | null;
|
|
457
564
|
/**
|
|
458
|
-
* Forces a fresh read -
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
565
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
566
|
+
* read.
|
|
567
|
+
*
|
|
568
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
569
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
570
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
571
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
572
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
573
|
+
* the same settled promise and reported the same stale answer forever -
|
|
574
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
575
|
+
* whose workspace had subscribed in a different one.
|
|
576
|
+
*
|
|
577
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
578
|
+
* entry that is already gone is a no-op.
|
|
463
579
|
*/
|
|
464
580
|
refetch(): void;
|
|
465
581
|
};
|
|
@@ -491,11 +607,20 @@ export declare function useConnectResource<T>(path: string, options?: Readonly<U
|
|
|
491
607
|
isLoading: boolean;
|
|
492
608
|
error: string | null;
|
|
493
609
|
/**
|
|
494
|
-
* Forces a fresh read -
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
610
|
+
* Forces a fresh read - evicts this path's cache entry and re-runs the
|
|
611
|
+
* read.
|
|
612
|
+
*
|
|
613
|
+
* It used only to re-trigger the effect, on the assumption that the caller
|
|
614
|
+
* had just evicted the entry itself through `mutateResource`. That made it
|
|
615
|
+
* useless for the other reason to refetch: **something changed somewhere
|
|
616
|
+
* this tab cannot see.** The provider's cache is a `Map` of resolved
|
|
617
|
+
* promises with no expiry, so a `refetch()` without the eviction re-read
|
|
618
|
+
* the same settled promise and reported the same stale answer forever -
|
|
619
|
+
* which is what left `<ConnectAccessGate>` showing "Choose a plan" in a tab
|
|
620
|
+
* whose workspace had subscribed in a different one.
|
|
621
|
+
*
|
|
622
|
+
* Still safe after a `mutateResource` against the same path: evicting an
|
|
623
|
+
* entry that is already gone is a no-op.
|
|
499
624
|
*/
|
|
500
625
|
refetch(): void;
|
|
501
626
|
};
|
|
@@ -77,6 +77,23 @@ export interface CheckoutUrlOptions {
|
|
|
77
77
|
subscriptionId?: string | null;
|
|
78
78
|
/** Carried into Connect's checkout, where it is validated against the real cart. */
|
|
79
79
|
coupon?: string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Where Connect sends the customer once the subscription is live - an
|
|
82
|
+
* absolute URL on your own origin.
|
|
83
|
+
*
|
|
84
|
+
* **Without it they do not come back.** Connect's default destination is
|
|
85
|
+
* the new subscription's own billing page, which is the right answer for
|
|
86
|
+
* someone who started in Connect and the wrong one for someone who started
|
|
87
|
+
* on your pricing page: they pay, land on a billing screen for a product
|
|
88
|
+
* they were in the middle of adopting, and nothing on it points home.
|
|
89
|
+
*
|
|
90
|
+
* Connect validates the origin against the redirect URIs registered for
|
|
91
|
+
* your Connect client and silently ignores anything else, so a value it
|
|
92
|
+
* does not recognise costs the return trip and never redirects your
|
|
93
|
+
* customer somewhere you did not register. Register the origin you intend
|
|
94
|
+
* to use; the path and query are yours.
|
|
95
|
+
*/
|
|
96
|
+
next?: string | null;
|
|
80
97
|
}
|
|
81
98
|
/**
|
|
82
99
|
* The Connect URL that cancels a subscription.
|
|
@@ -103,4 +120,4 @@ export declare function buildCancelUrl({ appUrl, subscriptionId }: {
|
|
|
103
120
|
* prefix that has never served this page. Building the other one is what
|
|
104
121
|
* sent every first-time subscriber from a partner's pricing page to a 404.
|
|
105
122
|
*/
|
|
106
|
-
export declare function buildCheckoutUrl({ appUrl, applicationId, planId, subscriptionId, coupon, }: CheckoutUrlOptions): string;
|
|
123
|
+
export declare function buildCheckoutUrl({ appUrl, applicationId, planId, subscriptionId, coupon, next, }: CheckoutUrlOptions): string;
|
package/dist/types/helpers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlanFeature, ReadinessBlocker, ReadinessResponse } from "./types.js";
|
|
1
|
+
import type { PlanFeature, PlanLimit, ReadinessBlocker, ReadinessResponse } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* The blockers that actually deny access, in the order to show them.
|
|
4
4
|
*
|
|
@@ -31,3 +31,38 @@ export type WirePlanFeature = string | PlanFeature;
|
|
|
31
31
|
* hand-rolled a bridge instead of using `createConnectHandler`.
|
|
32
32
|
*/
|
|
33
33
|
export declare function normalizePlanFeatures(features: readonly WirePlanFeature[] | undefined): PlanFeature[];
|
|
34
|
+
/**
|
|
35
|
+
* What a key the plan carries no answer for reads as - see
|
|
36
|
+
* `SubscribedPlan.limits`, which is sparse.
|
|
37
|
+
*
|
|
38
|
+
* `"none"` rather than `"unlimited"`: a plan that was never asked about a
|
|
39
|
+
* limit has not sold it, and defaulting the other way would hand an unbounded
|
|
40
|
+
* allowance to every tier that predates the question.
|
|
41
|
+
*/
|
|
42
|
+
export declare const NO_PLAN_LIMIT: PlanLimit;
|
|
43
|
+
/**
|
|
44
|
+
* One plan's answer for one limit key, with the sparse-map default applied.
|
|
45
|
+
*
|
|
46
|
+
* Indexing `plan.limits` directly is what makes a missing key `undefined`,
|
|
47
|
+
* which then reads as unlimited through the first `?.` somebody writes. This
|
|
48
|
+
* returns a `PlanLimit` either way.
|
|
49
|
+
*/
|
|
50
|
+
export declare function planLimitFor(limits: Readonly<Record<string, PlanLimit>> | undefined, key: string): PlanLimit;
|
|
51
|
+
/**
|
|
52
|
+
* Whether `additional` more of the resource fit under this limit, given how
|
|
53
|
+
* many the subscriber already has. The comparison, written once.
|
|
54
|
+
*
|
|
55
|
+
* Every way of writing it at a call site is wrong in a different direction:
|
|
56
|
+
* `count < limit.value` is off by one as soon as you add more than one row,
|
|
57
|
+
* `if (!limit.value)` refuses the unlimited tier, and
|
|
58
|
+
* `limit.value ?? Infinity` grants unlimited to a plan that allows none. This
|
|
59
|
+
* is the same implementation Connect's own server uses to validate a plan.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* const limit = await connect.entitlements.getLimit(workspaceId, "sites");
|
|
63
|
+
* if (!planLimitAllows(limit, await countSites(workspaceId))) {
|
|
64
|
+
* throw new Error("Your plan's site limit is reached.");
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function planLimitAllows(limit: PlanLimit, currentCount: number, additional?: number): boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
export { ConnectError, isConnectError } from "./errors.js";
|
|
17
17
|
export { blockingBlockers, normalizePlanFeatures } from "./helpers.js";
|
|
18
|
+
export { NO_PLAN_LIMIT, planLimitAllows, planLimitFor } from "./helpers.js";
|
|
18
19
|
export { arrayRemove, arrayUnion, deleteField, documentExists, increment, serverTimestamp, storeIndexFor, storeIndexSignature, unchangedSince, } from "./store.js";
|
|
19
20
|
export type { StoreIndexDirection, StoreIndexField } from "./store.js";
|
|
20
21
|
export { describeQueryRejection, storeIndexCovers } from "./store-schema.js";
|
|
@@ -21,6 +21,7 @@ import { AnalyticsResource } from "./resources/analytics.js";
|
|
|
21
21
|
import { OtpResource } from "./resources/otp.js";
|
|
22
22
|
import { SessionsResource } from "./resources/sessions.js";
|
|
23
23
|
import { PlatformResource } from "./resources/platform.js";
|
|
24
|
+
import { VisitorResource } from "./resources/visitor.js";
|
|
24
25
|
import { PushResource } from "./resources/push.js";
|
|
25
26
|
export declare const DEFAULT_BASE_URL = "https://connect.cosmoops.com";
|
|
26
27
|
export interface ConnectClientOptions {
|
|
@@ -111,6 +112,11 @@ export declare class ConnectClient {
|
|
|
111
112
|
readonly integrations: IntegrationsResource;
|
|
112
113
|
/** Which workspaces the signed-in user belongs to, switching between them, and the team - see `WorkspacesResource`. */
|
|
113
114
|
readonly workspaces: WorkspacesResource;
|
|
115
|
+
/**
|
|
116
|
+
* The public chat bubble on your own site - see `VisitorResource`. The one
|
|
117
|
+
* resource here whose caller is not one of your users.
|
|
118
|
+
*/
|
|
119
|
+
readonly visitor: VisitorResource;
|
|
114
120
|
/** The platform's own legal identity and DPDP grievance channel - see `PlatformResource`. */
|
|
115
121
|
readonly platform: PlatformResource;
|
|
116
122
|
/**
|
|
@@ -132,4 +138,4 @@ export declare class ConnectClient {
|
|
|
132
138
|
constructor(options: ConnectClientOptions);
|
|
133
139
|
}
|
|
134
140
|
/** Kept in step with `package.json` by `sdk.test.ts`. */
|
|
135
|
-
export declare const SDK_VERSION = "0.
|
|
141
|
+
export declare const SDK_VERSION = "0.34.0";
|
|
@@ -40,28 +40,4 @@ export interface ConnectHandlerOptions {
|
|
|
40
40
|
*/
|
|
41
41
|
stateCookieName?: string;
|
|
42
42
|
}
|
|
43
|
-
/**
|
|
44
|
-
* The browser half of this SDK cannot hold an API key - so it does not.
|
|
45
|
-
*
|
|
46
|
-
* `@cosmoops/connect-sdk/client` talks only to your own origin, and this
|
|
47
|
-
* handler is what answers. It lives in your backend, holds the key, and
|
|
48
|
-
* exposes a fixed, named set of reads plus sign-out and session revocation.
|
|
49
|
-
* Nothing here proxies arbitrary paths: a generic passthrough would hand the
|
|
50
|
-
* browser your whole API surface under your credentials, which is the same
|
|
51
|
-
* leak with extra steps.
|
|
52
|
-
*
|
|
53
|
-
* Framework-agnostic - `Request` in, `Response` out, so it mounts anywhere
|
|
54
|
-
* that shape exists.
|
|
55
|
-
*
|
|
56
|
-
* ```ts
|
|
57
|
-
* // app/api/connect/[...route]/route.ts
|
|
58
|
-
* const handler = createConnectHandler({ client, session });
|
|
59
|
-
* export { handler as GET, handler as POST };
|
|
60
|
-
* ```
|
|
61
|
-
*
|
|
62
|
-
* Every route resolves the workspace from the caller's *own* session, never
|
|
63
|
-
* from a query parameter. A browser must not be able to ask about an
|
|
64
|
-
* workspace it is not signed into, and a client-supplied `workspaceId`
|
|
65
|
-
* is exactly that hole.
|
|
66
|
-
*/
|
|
67
43
|
export declare function createConnectHandler(options: ConnectHandlerOptions): (request: Request) => Promise<Response>;
|