palbase 0.7.0 → 0.8.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.
Files changed (76) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +9 -9
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/5VTGMNPL.js +20 -0
  8. package/dist/chunks/{ZBOJEH7K.js → BO5KR2EQ.js} +2 -2
  9. package/dist/chunks/{PJVWGUIT.js → BRBCWPR3.js} +1 -1
  10. package/dist/chunks/DB3SIBKB.js +2 -0
  11. package/dist/chunks/EGNEXFTZ.js +2 -0
  12. package/dist/chunks/{HEXIIGWS.js → FZ3MQOVA.js} +2 -2
  13. package/dist/chunks/{SU7BP6VF.js → JSJVV43D.js} +1 -1
  14. package/dist/chunks/K6RRNI23.js +4 -0
  15. package/dist/chunks/{KDSWK3XW.js → KDWDPSL2.js} +1 -1
  16. package/dist/chunks/P4Z5E6ZI.js +2 -0
  17. package/dist/chunks/PDOHT46N.js +2 -0
  18. package/dist/chunks/PGPQGKH2.js +18 -0
  19. package/dist/chunks/{G3BK7L3J.js → RBA6V3UO.js} +2 -2
  20. package/dist/chunks/{X3MTLJRV.js → RO5Y5YU4.js} +1 -1
  21. package/dist/chunks/{BKVU4I5R.js → TUGRIOFX.js} +2 -2
  22. package/dist/chunks/WEI32SAJ.js +13 -0
  23. package/dist/chunks/{5ZJCEQV5.js → WINBISC2.js} +3 -3
  24. package/dist/chunks/Z5JVQNAZ.js +2 -0
  25. package/dist/cli.js +121 -120
  26. package/dist/refuse/ai.js +1 -1
  27. package/dist/refuse/backend.js +1 -1
  28. package/dist/refuse/server.js +1 -1
  29. package/dist/server/index.js +2 -2
  30. package/dist/server/testing.js +2 -2
  31. package/dist/web/index.js +2 -2
  32. package/dist/web/palbe.js +2 -2
  33. package/dist/web/paywall.js +2 -2
  34. package/dist/web/ssr.js +2 -2
  35. package/dist/web/vite.js +2 -2
  36. package/package.json +2 -2
  37. package/types/ai/src/ai.d.ts +44 -15
  38. package/types/ai/src/chats.d.ts +442 -12
  39. package/types/ai/src/index.d.ts +4 -4
  40. package/types/ai/src/tools.d.ts +158 -21
  41. package/types/backend/src/bootstrap.d.ts +8 -0
  42. package/types/backend/src/container.d.ts +9 -0
  43. package/types/backend/src/database.d.ts +17 -2
  44. package/types/backend/src/engine.d.ts +2 -0
  45. package/types/backend/src/gateways.d.ts +19 -0
  46. package/types/backend/src/index.d.ts +1 -0
  47. package/types/backend/src/runtime.d.ts +12 -0
  48. package/types/backend/src/testing/unit.d.ts +3 -1
  49. package/types/internal/src/chat.d.ts +168 -3
  50. package/types/internal/src/modules/ai.d.ts +1305 -57
  51. package/types/internal/src/modules/auth.d.ts +14 -2
  52. package/types/internal/src/modules/events.d.ts +2 -2
  53. package/types/internal/src/modules/machines.d.ts +57 -1
  54. package/types/internal/src/modules/notify.d.ts +6 -6
  55. package/types/internal/src/modules/purchases.d.ts +357 -13
  56. package/types/internal/src/modules/realtime.d.ts +36 -0
  57. package/types/internal/src/modules/stores.d.ts +259 -22
  58. package/types/internal/src/transport.d.ts +5 -0
  59. package/types/server/src/auth/jwt.d.ts +1 -6
  60. package/types/web/src/auth.d.ts +15 -4
  61. package/types/web/src/backends.d.ts +1 -1
  62. package/types/web/src/chat.d.ts +215 -4
  63. package/types/web/src/client.d.ts +28 -0
  64. package/types/web/src/gateways.d.ts +4 -3
  65. package/types/web/src/index.d.ts +3 -2
  66. package/types/web/src/session.d.ts +11 -1
  67. package/types/web/src/voice.d.ts +36 -0
  68. package/dist/chunks/3ALGILOX.js +0 -2
  69. package/dist/chunks/AIIKXCWN.js +0 -2
  70. package/dist/chunks/ELEE7XO2.js +0 -18
  71. package/dist/chunks/G7E26S3Y.js +0 -13
  72. package/dist/chunks/H2EPK74V.js +0 -20
  73. package/dist/chunks/JS3TTUHS.js +0 -2
  74. package/dist/chunks/L3NTI4AZ.js +0 -2
  75. package/dist/chunks/LEBVOSG6.js +0 -4
  76. package/dist/chunks/OIA7KLRW.js +0 -2
@@ -1,5 +1,23 @@
1
- import { type AgentEvent, type ChatEvent, type ToolEvent, type TurnEvent } from '../../internal/src/chat.js';
2
- export type { AgentEvent, ChatEvent, ToolEvent, TurnEvent } from '../../internal/src/chat.js';
1
+ import { type AgentEvent, type ChatEvent, type GoalEvent, type ToolEvent, type TurnEnd, type TurnEvent, type VoiceEvent } from '../../internal/src/chat.js';
2
+ import { type Transport } from '../../internal/src/transport.js';
3
+ export type { AgentEvent, ChatEvent, GoalEvent, HistoryEvent, HistoryItem, Publication, ThinkingEvent, ToolEvent, TranscriptEvent, TurnEnd, TurnEvent, VoiceEvent } from '../../internal/src/chat.js';
4
+ /** A picture or a file a message carries: an image the model was shown, or a file put in the chat's workspace. */
5
+ export interface ChatAttachment {
6
+ kind: 'image' | 'file' | (string & {});
7
+ /** What the message calls it: a file's name in the workspace. */
8
+ name: string;
9
+ contentType: string;
10
+ size: number;
11
+ /** Its bytes' sha256, hex. */
12
+ sha256: string;
13
+ /** Its place among the message's attachments (its images, then its files): what its bytes are read by. */
14
+ position: number;
15
+ /** Where it was copied from in the project's storage. */
16
+ bucket: string;
17
+ path: string;
18
+ /** A file's place in the chat's workspace (.palbase/attachments/<message id>/<name>); null for a picture. */
19
+ workspacePath: string | null;
20
+ }
3
21
  /** A member's (or the server's) message, as the agent got it. */
4
22
  export interface ChatMessageView {
5
23
  /** The writer's id for it: the same id is the same message. */
@@ -19,8 +37,37 @@ export interface ChatMessageView {
19
37
  createdAt: string;
20
38
  /** The signed-in user wrote it. */
21
39
  mine: boolean;
40
+ /** Its writer's labels for it ({} for none): what an app draws it by. */
41
+ metadata: Record<string, string>;
42
+ /** It is of what a fork began with: the chat it was forked from had it. */
43
+ history: boolean;
44
+ /** Its text is the first of a longer one (a fork's history part carries no more): the fork's model read it whole. */
45
+ truncated: boolean;
46
+ /** text: written to the chat; voice: a line said out loud in its voice — the person's (author, the member) or the voice's (author null). */
47
+ kind: 'text' | 'voice' | (string & {});
48
+ /** A line of kind voice: the voice it was said in, and by whom (user: the person; assistant: the voice). */
49
+ voice: {
50
+ id: string;
51
+ role: 'user' | 'assistant' | (string & {});
52
+ } | null;
53
+ /** Its pictures and files, its images first ([] for none). */
54
+ attachments: ChatAttachment[];
55
+ }
56
+ /** One model step's reasoning, as the agent showed it (thinking: visible): its windows as they came, together. */
57
+ export interface ChatThinking {
58
+ /** The model step it is of; null when the part named none. */
59
+ stepId: string | null;
60
+ runId: string;
61
+ text: string;
62
+ /**
63
+ * The agent shows its reasoning and the turn's provider would not give it: why there is none (text ''), said
64
+ * once, where it would have been — the model, and the provider's word (null otherwise).
65
+ */
66
+ unavailable: boolean;
67
+ model: string | null;
68
+ reason: string | null;
22
69
  }
23
- /** A piece of an answer, in order: its text, split where a tool was called or a subagent handed work. */
70
+ /** A piece of an answer, in order: its text, split where a tool was called, a subagent handed work, or a step's reasoning began. */
24
71
  export type ChatPart = {
25
72
  type: 'text';
26
73
  text: string;
@@ -30,12 +77,18 @@ export type ChatPart = {
30
77
  } | {
31
78
  type: 'agent';
32
79
  agent: AgentEvent;
80
+ } | {
81
+ type: 'thinking';
82
+ thinking: ChatThinking;
33
83
  };
34
84
  /** One answer of the agent's (or of one of its subagents). */
35
85
  export interface ChatResponse {
36
86
  id: string;
37
87
  status: TurnEvent['status'] | null;
38
88
  waiting: TurnEvent['waiting'];
89
+ /** How its turn ended, in the chat's words — completed, failed, stopped, budget_exceeded; null while it has not (or a subagent's). */
90
+ end: TurnEnd | (string & {}) | null;
91
+ /** Why, when it did not complete and was not stopped: its code, its words, whether asking again may go otherwise. */
39
92
  error: TurnEvent['error'];
40
93
  /** The whole answer so far. */
41
94
  text: string;
@@ -48,13 +101,105 @@ export interface ChatResponse {
48
101
  startedBy: string | null;
49
102
  /** The signed-in user's own turn. */
50
103
  mine: boolean;
104
+ /** The model's reasoning, step by step, when the agent shows it — also in parts, where each step's began. */
105
+ thinking: ChatThinking[];
106
+ /** A turn the chat's goal opened (a subagent's: its turn's): the goal, the turn's role and its number. */
107
+ goal: {
108
+ goalId: string;
109
+ role: string;
110
+ seq: number;
111
+ } | null;
112
+ /** It is of what a fork began with: the agent's answer in the chat it was forked from. */
113
+ history: boolean;
114
+ /** Its text is the first of a longer one (a fork's history part carries no more). */
115
+ truncated: boolean;
116
+ }
117
+ /** One tool call of the chat, its whole answer — the stream shows its end (its last 16 KiB) — its secret shapes masked. */
118
+ export interface ChatToolCall {
119
+ callId: string;
120
+ name: string;
121
+ status: string;
122
+ runId: string;
123
+ responseId: string;
124
+ /** What the agent called it with; null when its arguments, masked, are no JSON. */
125
+ input: unknown;
126
+ /** Its answer's text, whole; null: none yet. */
127
+ output: string | null;
128
+ outputBytes: number;
129
+ imageCount: number;
130
+ isError: boolean;
131
+ }
132
+ /** One part of a chat's stream as a page of its past holds it (palbase/ai's chats.parts, or the module's own page). */
133
+ export interface ChatStreamPart {
134
+ type: string;
135
+ id?: number;
136
+ data: Record<string, unknown>;
137
+ }
138
+ /** A page of a chat's past, the oldest it holds first; nextCursor reads the page before it (none: the chat's start is in this one). */
139
+ export interface ChatPartsPage {
140
+ parts: ChatStreamPart[];
141
+ nextCursor?: string;
142
+ }
143
+ /**
144
+ * Which page of a chat's past is read: before a part (before: its journal's sequence — the oldest the channel gave),
145
+ * where a page's nextCursor left off (cursor), or from the chat's newest end (neither).
146
+ */
147
+ export interface ChatPartsQuery {
148
+ before?: number;
149
+ cursor?: string;
150
+ limit?: number;
151
+ signal?: AbortSignal;
152
+ }
153
+ /** How a chat reads its past past the day its channel keeps: the module, with the member's own session — or the app's own way. */
154
+ export interface ChatHistoryReader {
155
+ parts(chatId: string, query: ChatPartsQuery): Promise<ChatPartsPage>;
156
+ }
157
+ /** The chat's goal as its words told it: set, changed, its status, and each word in order. */
158
+ export interface ChatGoal {
159
+ id: string;
160
+ objective: string;
161
+ rubric: string;
162
+ /** active, paused, blocked, budget_limited, achieved — or cleared. */
163
+ status: string;
164
+ /** Why it last left active, as a stable code (empty while active). */
165
+ stopCode: string;
166
+ stopReason: string;
167
+ /** What its next turn is, as it was last said: work, or verify. */
168
+ phase: string;
169
+ maxTurns: number | null;
170
+ maxCostUsd: number | null;
171
+ /** Its words as they came: set, changed, each turn begun, judged and checked, its status, cleared. */
172
+ events: GoalEvent[];
51
173
  }
174
+ /** The chat's voice as its parts told it: whose it is, what becomes of what is asked of it, live or ended — and why. */
175
+ export interface ChatVoice {
176
+ id: string;
177
+ /** Its member; null: the server's own. */
178
+ userId: string | null;
179
+ /** The signed-in user's own. */
180
+ mine: boolean;
181
+ delegation: VoiceEvent['delegation'];
182
+ status: 'started' | 'closed' | (string & {});
183
+ /** On closed: how long it lasted, and why it ended ('' while live). */
184
+ seconds: number | null;
185
+ closeReason: string;
186
+ }
187
+ /**
188
+ * One thing of the conversation, in order: a message (a line said in the chat's voice too: kind voice), an answer, a
189
+ * word of the chat's goal, or the chat's voice beginning or ending.
190
+ */
52
191
  export type ChatItem = {
53
192
  type: 'message';
54
193
  message: ChatMessageView;
55
194
  } | {
56
195
  type: 'response';
57
196
  response: ChatResponse;
197
+ } | {
198
+ type: 'goal';
199
+ goal: GoalEvent;
200
+ } | {
201
+ type: 'voice';
202
+ voice: VoiceEvent;
58
203
  };
59
204
  export interface ChatState {
60
205
  /** The conversation in order: the members' messages and the agent's answers (a subagent's is in its parent's parts). */
@@ -74,6 +219,15 @@ export interface ChatState {
74
219
  approvals: ToolEvent[];
75
220
  outputs: ToolEvent[];
76
221
  };
222
+ /** The chat's goal, once one was set in it; null: none. */
223
+ goal: ChatGoal | null;
224
+ /** A fork's: the chat it was forked from, and the message it was cut before; null for a chat of its own. */
225
+ forkedFrom: {
226
+ chatId: string;
227
+ beforeMessage: string;
228
+ } | null;
229
+ /** The chat's voice — the last that began in it, live or ended; null: none yet. */
230
+ voice: ChatVoice | null;
77
231
  }
78
232
  /** A gateway's chat: its state now, and told each time it changes. */
79
233
  export interface GatewayChat extends AsyncIterable<ChatState> {
@@ -81,6 +235,21 @@ export interface GatewayChat extends AsyncIterable<ChatState> {
81
235
  readonly state: ChatState;
82
236
  /** Told each time the state changes (at most once a microtask); returns the unsubscribe. */
83
237
  subscribe(listener: (state: ChatState) => void): () => void;
238
+ /** More of the chat's past is there to read with loadOlder (true until a page reaches the chat's start). */
239
+ readonly hasOlder: boolean;
240
+ /**
241
+ * Reads the page of the chat's past before what it holds — the channel keeps a day — and folds it in: the first
242
+ * page is the one before the oldest part the channel gave (before: its sequence; none: the chat's newest end),
243
+ * each after it the page its last named. With the member's own session (the project lets its app's users reach
244
+ * their chats: ai.settings clients) unless load gives the page another way — the app's backend asking palbase/ai's
245
+ * chats.parts. Resolves with hasOlder. The chat's id is the join's chatId, or its messages' (chatId gives it).
246
+ */
247
+ loadOlder(options?: {
248
+ limit?: number;
249
+ chatId?: string;
250
+ load?: (query: ChatPartsQuery) => Promise<ChatPartsPage>;
251
+ signal?: AbortSignal;
252
+ }): Promise<boolean>;
84
253
  }
85
254
  /**
86
255
  * A chat folded from its channel's parts. It holds no connection: a part is
@@ -89,18 +258,47 @@ export interface GatewayChat extends AsyncIterable<ChatState> {
89
258
  */
90
259
  export declare class ChatLog implements GatewayChat {
91
260
  private readonly me;
261
+ private readonly reader;
92
262
  private readonly messages;
93
263
  private readonly responses;
94
264
  private readonly order;
95
265
  private turn;
266
+ private goal;
267
+ /** The goal's words taken, by their sequence. */
268
+ private readonly goalWords;
269
+ /** The voices' starts and ends taken, each once by its voice: an end is told under no sequence (vclosed:<voice>). */
270
+ private readonly voiceWords;
271
+ /** The chat's voice: the last that began in it, live or ended. */
272
+ private voice;
273
+ /** A fork's history, by its part's place: whenever a part comes, what it holds goes before the rest of the chat. */
274
+ private readonly history;
275
+ private forkedFrom;
96
276
  private readonly calls;
277
+ /**
278
+ * Every part the channel gave, in order, and the pages of the past read before them (loadOlder), the oldest first:
279
+ * a page read is folded by folding them all again, the past first — a part both told (the channel's day and the
280
+ * page's newest end meet) is then the channel's, as it came after.
281
+ */
282
+ private readonly taken;
283
+ private older;
284
+ /** The oldest part the channel gave, by its journal's sequence: the first page of the past is read before it. */
285
+ private oldestLive;
286
+ private olderCursor;
287
+ private olderLeft;
288
+ private loading;
289
+ private chatId;
97
290
  private readonly listeners;
98
291
  /** The iterations waiting for a change: woken when the gateway is left. */
99
292
  private readonly enders;
100
293
  private snapshot;
101
294
  private told;
102
295
  private ended;
103
- constructor(me?: () => string | undefined);
296
+ constructor(me?: () => string | undefined, reader?: ChatHistoryReader, chatId?: string);
297
+ get hasOlder(): boolean;
298
+ loadOlder(options?: Parameters<GatewayChat['loadOlder']>[0]): Promise<boolean>;
299
+ private readOlder;
300
+ private idFor;
301
+ private refold;
104
302
  get state(): ChatState;
105
303
  subscribe(listener: (state: ChatState) => void): () => void;
106
304
  /** The state now, then each time it changes, until the gateway is left. */
@@ -110,9 +308,22 @@ export declare class ChatLog implements GatewayChat {
110
308
  /** One payload of the channel's "chat" event; one that is no part this runtime reads is let be. */
111
309
  takePayload(payload: unknown): void;
112
310
  take(e: ChatEvent): void;
311
+ private fold;
312
+ private goalSaid;
113
313
  private heard;
314
+ private said;
114
315
  private place;
115
316
  private response;
116
317
  private changed;
117
318
  private build;
118
319
  }
320
+ /**
321
+ * A chat's member's reads of it beyond its channel, with their own session — the ai module's calls written here, so
322
+ * a browser's bundle carries nothing more of the module. The project lets its app's users reach their chats
323
+ * (ai.settings clients), or the module refuses them (403 clients_disabled).
324
+ */
325
+ export interface ChatReads extends ChatHistoryReader {
326
+ toolCall(chatId: string, callId: string, signal?: AbortSignal): Promise<ChatToolCall>;
327
+ workspaceFile(chatId: string, path: string, manifest?: string, signal?: AbortSignal): Promise<Blob>;
328
+ }
329
+ export declare function chatReader(transport: Transport): ChatReads;
@@ -5,6 +5,7 @@ import { type RefreshMode, type SessionStore } from './session.js';
5
5
  import { RealtimeSocket } from './socket.js';
6
6
  import { Analytics, type AnalyticsOptions } from './analytics.js';
7
7
  import { Purchases } from './purchases.js';
8
+ import { type ChatToolCall } from './chat.js';
8
9
  export interface ClientOptions {
9
10
  /** The project's address: https://<ref>.<domain> */
10
11
  url: string;
@@ -45,6 +46,14 @@ export interface ClientOptions {
45
46
  * which app asks, it does not prove it. Its id, not its slug: a UUID.
46
47
  */
47
48
  appId?: string;
49
+ /**
50
+ * The language the app shows itself in, said with every request as
51
+ * Accept-Language — a tag ("tr", "pt-BR"), or tags in order of preference
52
+ * — what auth's emails and pages, a flag's and a paywall's rules and a
53
+ * backend's translations read. Without it the browser says its own.
54
+ * pb.setLanguage changes it.
55
+ */
56
+ language?: string | readonly string[];
48
57
  }
49
58
  /**
50
59
  * The app's client. A backend's generated Palbe module (palbase apps:sync)
@@ -62,5 +71,24 @@ export interface PalbaseClient {
62
71
  analytics: Analytics;
63
72
  /** What the signed-in customer has (live, onCustomerInfo), the paywall's catalog, and buying on the web. */
64
73
  purchases: Purchases;
74
+ /** The language every request says from now on (ClientOptions.language); null for the browser's own. */
75
+ setLanguage(language: string | readonly string[] | null): void;
76
+ /**
77
+ * One tool call of a chat the signed-in user is a member of, its whole answer — the one a tool part names
78
+ * (call_id): the chat's channel shows a call's answer by its end (omittedBytes what it left out). Read with their
79
+ * own session (the project lets its app's users reach their chats: ai.settings clients); another chat's call is
80
+ * not found.
81
+ */
82
+ chatToolCall(chatId: string, callId: string, options?: {
83
+ signal?: AbortSignal;
84
+ }): Promise<ChatToolCall>;
85
+ /**
86
+ * A file of a chat's workspace, its bytes: from the tree the workspace is at now, or from the one a publish named
87
+ * (manifest: an ai.workspace.published event's manifest_id) — a build's product as that publish left it. Up to
88
+ * 64 MiB (file_too_large past it); a path its tree does not name is file_not_found.
89
+ */
90
+ chatWorkspaceFile(chatId: string, path: string, manifest?: string, options?: {
91
+ signal?: AbortSignal;
92
+ }): Promise<Blob>;
65
93
  }
66
94
  export declare function createClient(opts: ClientOptions): PalbaseClient;
@@ -1,4 +1,4 @@
1
- import { type GatewayChat } from './chat.js';
1
+ import { type ChatHistoryReader, type GatewayChat } from './chat.js';
2
2
  import type { RealtimeSocket } from './socket.js';
3
3
  /** One event of a gateway, as a generated module's union has it. */
4
4
  export type GatewayEvent = {
@@ -84,6 +84,7 @@ export declare class GatewayError extends Error {
84
84
  /**
85
85
  * Joins `gateway` on `socket`: resolves once the backend let the user in,
86
86
  * rejects with a GatewayError when it did not. me is the signed-in user: a
87
- * chat's turns and messages that are theirs.
87
+ * chat's turns and messages that are theirs; reader is how a chat gateway's
88
+ * chat reads its past and its calls with the user's session.
88
89
  */
89
- export declare function joinGateway<E extends GatewayEvent, S, R, P extends readonly string[], C extends boolean = false>(socket: RealtimeSocket, gateway: GatewayDescriptor<E, S, R, P, C>, params: Record<P[number], string>, options?: JoinOptions, me?: () => string | undefined): Promise<JoinedGateway<E, S, R, C>>;
90
+ export declare function joinGateway<E extends GatewayEvent, S, R, P extends readonly string[], C extends boolean = false>(socket: RealtimeSocket, gateway: GatewayDescriptor<E, S, R, P, C>, params: Record<P[number], string>, options?: JoinOptions, me?: () => string | undefined, reader?: ChatHistoryReader): Promise<JoinedGateway<E, S, R, C>>;
@@ -1,9 +1,10 @@
1
1
  export { createClient, type ClientOptions, type PalbaseClient } from './client.js';
2
2
  export type { Auth, CredentialsLike, SignInResult, User } from './auth.js';
3
- export { localStore, memoryStore, SessionChangedError, type AuthEvent, type RefreshMode, type Session, type SessionStore } from './session.js';
3
+ export { localStore, memoryStore, SessionChangedError, type AuthEvent, type RefreshMode, type Session, type SessionEndReason, type SessionStore } from './session.js';
4
4
  export { Channel, RealtimeSocket, type ChannelOptions, type PushReply } from './socket.js';
5
5
  export { Analytics, type AnalyticsOptions } from './analytics.js';
6
- export type { AgentEvent, ChatEvent, ChatItem, ChatMessageView, ChatPart, ChatResponse, ChatState, GatewayChat, ToolEvent, TurnEvent } from './chat.js';
6
+ export type { AgentEvent, ChatAttachment, ChatEvent, ChatGoal, ChatItem, ChatMessageView, ChatPart, ChatPartsPage, ChatPartsQuery, ChatResponse, ChatState, ChatStreamPart, ChatThinking, ChatToolCall, ChatVoice, GatewayChat, GoalEvent, HistoryEvent, HistoryItem, Publication, ThinkingEvent, ToolEvent, TranscriptEvent, TurnEnd, TurnEvent, VoiceEvent, } from './chat.js';
7
+ export { connectVoice, type StartedVoice, type VoiceConnection, type VoiceOptions } from './voice.js';
7
8
  export { Purchases, type CustomerInfo, type CustomerInfoListener, type MergeGrant, type Offerings } from './purchases.js';
8
9
  export type { Paywall, PaywallRenderOptions } from './paywall/placement.js';
9
10
  export type { PaywallAttribute } from './paywall/attributes.js';
@@ -17,6 +17,14 @@ export interface SessionStore {
17
17
  watch?(onChange: () => void): () => void;
18
18
  }
19
19
  export type AuthEvent = 'SIGNED_IN' | 'TOKEN_REFRESHED' | 'USER_UPDATED' | 'SIGNED_OUT' | 'SESSION_EXPIRED';
20
+ /**
21
+ * Why a session ended without the app signing out (SESSION_EXPIRED): it ran out (past its lifetime, or idle too
22
+ * long), it was revoked (signed out everywhere, revoked by the project, its token refused), the account was deleted
23
+ * (by the project, or by the user on another device), or the account was suspended.
24
+ */
25
+ export type SessionEndReason = 'expired' | 'revoked' | 'userDeleted' | 'userBanned';
26
+ /** What a refused refresh says: auth's reason when it gives one, else what its code means. */
27
+ export declare function sessionEndReasonOf(refusal: unknown): SessionEndReason;
20
28
  export interface TokenPair {
21
29
  access_token: string;
22
30
  refresh_token: string;
@@ -57,6 +65,8 @@ export declare class SessionManager {
57
65
  private current;
58
66
  private loaded;
59
67
  private inflight;
68
+ /** Why the last session ended, set before SESSION_EXPIRED is told; null once someone signs in or out. */
69
+ endReason: SessionEndReason | null;
60
70
  private listeners;
61
71
  private store;
62
72
  private refresher;
@@ -82,7 +92,7 @@ export declare class SessionManager {
82
92
  * tab: a sign-out signs the user out of every tab. It is left alone only
83
93
  * when it holds another user's (someone else signed in since).
84
94
  */
85
- clear(event: 'SIGNED_OUT' | 'SESSION_EXPIRED'): Promise<void>;
95
+ clear(event: 'SIGNED_OUT' | 'SESSION_EXPIRED', reason?: SessionEndReason): Promise<void>;
86
96
  /** The access token to send now; undefined when no one is signed in. */
87
97
  accessToken(): Promise<string | undefined>;
88
98
  /** One refresh at a time — in this tab, and across tabs when the store is shared. */
@@ -0,0 +1,36 @@
1
+ /** What the app's backend answers the offer with: the voice it started, and the provider's answer (chats.voice.start). */
2
+ export interface StartedVoice {
3
+ id: string;
4
+ sdp: string;
5
+ }
6
+ export interface VoiceOptions {
7
+ /** The round trip to the app's own backend: the offer (an SDP) in, the voice and its answer out. */
8
+ start(sdp: string): Promise<StartedVoice>;
9
+ /** Where the voice plays; left out, an audio element of its own (autoplay), not in the page. */
10
+ audio?: HTMLAudioElement;
11
+ /** A microphone the app opened already — left open on close; left out, one is asked for (getUserMedia) and closed with the voice. */
12
+ microphone?: MediaStream;
13
+ /** How long the offer's network candidates are gathered at most before it is sent (default 10 s). */
14
+ iceTimeoutMs?: number;
15
+ /** Ends the connecting: whatever was opened is closed. */
16
+ signal?: AbortSignal;
17
+ /** The provider's word that something went wrong in the voice (its events channel carries errors alone). */
18
+ onError?(message: string): void;
19
+ /** The connection's state each time it changes: connected, disconnected, failed, closed. */
20
+ onState?(state: RTCPeerConnectionState): void;
21
+ }
22
+ /** A voice connected: the peer connection, where it plays, and its microphone — muted, or closed. */
23
+ export interface VoiceConnection {
24
+ /** The voice the backend started: what it reads, tells and closes it by. */
25
+ readonly id: string;
26
+ readonly peer: RTCPeerConnection;
27
+ readonly audio: HTMLAudioElement;
28
+ readonly microphone: MediaStream;
29
+ /** The microphone is heard (false) or not (true). */
30
+ readonly muted: boolean;
31
+ mute(on: boolean): void;
32
+ /** Ends it here: the connection, and the microphone it opened. The backend closes the voice itself (chats.voice.close). */
33
+ close(): void;
34
+ }
35
+ /** Connects the browser to a chat's voice: the microphone in, the voice out, the offer through the app's own backend. */
36
+ export declare function connectVoice(options: VoiceOptions): Promise<VoiceConnection>;
@@ -1,2 +0,0 @@
1
- /*! palbase 0.7.0 — Palbase SDK License (see LICENSE) */
2
- import{f as D,g as K}from"./G7E26S3Y.js";import{u as F}from"./5ZJCEQV5.js";import{b as O,c as m,h as j}from"./AIIKXCWN.js";import{a as q,b as w}from"./ZBOJEH7K.js";import{b as x,d as v}from"./JS3TTUHS.js";var p=class extends Error{code="session_changed";constructor(e){super(e),this.name="SessionChangedError"}};function $(){let n=null;return{load:()=>n,save:e=>{n=e},clear:()=>{n=null},shared:!1}}function H(n){let e=null,t=!0,s=()=>{try{return globalThis.localStorage}catch{return}};return{shared:!0,load(){try{let i=s()?.getItem(n);return i==null?t?null:e:JSON.parse(i)}catch{return e}},save(i){e=i;try{let r=s();if(!r)throw new Error("no storage");r.setItem(n,JSON.stringify(i)),t=!0}catch{t=!1}},clear(){e=null,t=!0;try{s()?.removeItem(n)}catch{}},watch(i){let r=globalThis;if(typeof r.addEventListener!="function")return()=>{};let a=o=>{(o.key===n||o.key===null)&&i()};return r.addEventListener("storage",a),()=>r.removeEventListener?.("storage",a)}}}function Z(n){try{let e=n.split(".")[1]??"",t=atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(e.length/4)*4,"=")),s=JSON.parse(t).sub;return typeof s=="string"?s:""}catch{return""}}var ee=6e4,te=15e3;function se(n,e,t){let s=n?globalThis.navigator?.locks:void 0;return s?s.request(e,t):t()}var b=class{current=null;loaded;inflight=null;listeners=new Set;store;refresher;isRejected;now;lockName;refreshTimeoutMs;mode;constructor(e,t,s,i={}){this.store=e,this.refresher=t,this.isRejected=s,this.now=i.now??Date.now,this.lockName=i.lockName??"palbase.session.refresh",this.refreshTimeoutMs=i.refreshTimeoutMs??te,this.mode=i.refresh??"behind",this.loaded=Promise.resolve(e.load()).then(r=>{this.current=r}),e.watch?.(()=>{this.reconcile()})}async session(){return await this.loaded,this.current}snapshot(){return this.current}onChange(e){return this.listeners.add(e),()=>this.listeners.delete(e)}async set(e,t,s){await this.loaded;let i=t==="TOKEN_REFRESHED"?this.current:null,r={accessToken:e.access_token,refreshToken:e.refresh_token,expiresAt:this.now()+e.expires_in*1e3,userId:s?.id??i?.userId??Z(e.access_token),user:s??i?.user};return this.current=r,await this.store.save(r),this.emit(t,r),r}async setUser(e,t){await this.loaded,!(!this.current||!t||this.current.userId!==t.userId)&&(this.current={...this.current,user:e},await this.store.save(this.current),this.emit("USER_UPDATED",this.current))}async clear(e){await this.loaded;let t=this.current;this.current=null,this.inflight=null;let s=await this.store.load();(!s||!t||s.userId===t.userId)&&await this.store.clear(),t&&this.emit(e,null)}async accessToken(){await this.loaded;let e=this.current;if(!e)return;let t=e.expiresAt-this.now();if(t>ee)return e.accessToken;if(this.mode==="never")return t>0?e.accessToken:void 0;if(t>0&&this.mode==="behind")return this.refresh().catch(()=>{}),e.accessToken;if(t>0)try{return(await this.refresh()).accessToken}catch(s){if(this.current===e)return e.accessToken;throw s}return(await this.refresh()).accessToken}refresh(){if(this.mode==="never")return Promise.reject(new Error("this client does not renew its session (refresh: 'never'): where the session cannot be written back, the request's middleware renews it"));if(this.inflight)return this.inflight;let e=se(this.store.shared===!0,this.lockName,async()=>{await this.loaded;let t=this.current;if(!t)throw new p("no session to refresh");let s=await this.adoptStored(t);if(s)return s;try{let i=await this.withTimeout(this.refresher(t.refreshToken));if(this.current?.refreshToken!==t.refreshToken)throw new p("the session changed while it was refreshed");return await this.set(i,"TOKEN_REFRESHED")}catch(i){throw this.isRejected(i)&&this.current?.refreshToken===t.refreshToken&&await this.clear("SESSION_EXPIRED"),i}});return this.inflight=e,e.finally(()=>{this.inflight===e&&(this.inflight=null)}).catch(()=>{}),e}async adoptStored(e){let t=await this.store.load();if(!t)throw this.current=null,this.emit("SIGNED_OUT",null),new p("signed out in another tab");if(t.userId!==e.userId)throw this.current=t,this.emit("SIGNED_OUT",null),this.emit("SIGNED_IN",t),new p("another user signed in in another tab");if(t.refreshToken!==e.refreshToken&&t.expiresAt>=e.expiresAt)return this.current=t,this.emit("TOKEN_REFRESHED",t),t}async reconcile(){await this.loaded;let e=this.current,t=await this.store.load();if(!e){t&&(this.current=t,this.emit("SIGNED_IN",t));return}await this.adoptStored(e).catch(()=>{})}withTimeout(e){let t,s=new Promise((i,r)=>{t=setTimeout(()=>r(new Error(`the refresh did not answer within ${this.refreshTimeoutMs/1e3} s`)),this.refreshTimeoutMs)});return Promise.race([e,s]).finally(()=>clearTimeout(t))}emit(e,t){for(let s of this.listeners)try{s(e,t)}catch{}}};var M=class{state="closed";refusal;logSeq;joinRef=null;listeners=new Map;closers=new Set;joinWaiters=[];name;socket;constructor(e,t,s={}){this.name=e,this.socket=t,this.logSeq=s.after}get topic(){return`realtime:${this.name}`}join(){if(this.state==="joined")return Promise.resolve();let e=new Promise((t,s)=>this.joinWaiters.push({resolve:t,reject:s}));return this.state!=="joining"&&this.socket.join(this),e}leave(){this.socket.leave(this)}push(e,t){return this.socket.request(this,e,t)}on(e,t){let s=this.listeners.get(e);return s||this.listeners.set(e,s=new Set),s.add(t),()=>s.delete(t)}onClosed(e){return this.closers.add(e),()=>this.closers.delete(e)}deliver(e,t){e==="broadcast"&&typeof t.seq=="number"&&(this.logSeq=t.seq);for(let s of this.listeners.get(e)??[])try{s(t)}catch{}}settled(e,t){e&&typeof t.log_seq=="number"&&(this.logSeq=t.log_seq),this.state=e?"joined":"refused",this.refusal=e?void 0:t;let s=this.joinWaiters.splice(0);for(let i of s)e?i.resolve():i.reject(new Error(`join ${this.name} refused: ${JSON.stringify(t)}`))}closed(e){this.state="closed",this.joinRef=null;let t=this.joinWaiters.splice(0);for(let s of t)s.reject(new Error(`join ${this.name}: ${e}`));for(let s of[...this.closers])try{s()}catch{}}},k=class{ws=null;opening=null;ref=0;channels=new Map;replies=new Map;heartbeat=null;heartbeatRef=null;reconnectTimer=null;attempts=0;closedByUs=!1;reconnects=0;opts;constructor(e){this.opts=e}channel(e,t){let s=this.channels.get(e);return s||this.channels.set(e,s=new M(e,this,t)),s}disconnect(){this.closedByUs=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=null,this.stopHeartbeat();let e=this.ws;this.ws=null,this.opening=null,e?.close(1e3);for(let[,t]of this.replies)t("error",{reason:"socket closed"});this.replies.clear();for(let t of this.channels.values())t.closed("the socket was closed")}async tokenChanged(){if(!this.ws||this.ws.readyState!==1)return;let e=await this.opts.accessToken();if(e)for(let t of this.channels.values())t.state==="joined"&&this.push(t.joinRef,t.topic,"access_token",{access_token:e})}join(e){e.state="joining",this.open().then(()=>this.sendJoin(e)).catch(t=>e.settled(!1,{reason:t instanceof Error?t.message:String(t)}))}leave(e){e.state==="joined"&&this.ws?.readyState===1&&this.push(e.joinRef,e.topic,"phx_leave",{}),e.state="closed",this.channels.delete(e.name)}async request(e,t,s){let i=this.ws;if(this.channels.get(e.name)!==e||e.state!=="joined"||i?.readyState!==1)return{status:"error",response:{reason:"not joined"}};let r=this.nextRef(),a=JSON.stringify([e.joinRef,r,e.topic,t,s]),o=this.awaitReply(r);return i.send(a),o}open(){if(this.ws?.readyState===1)return Promise.resolve();if(this.opening)return this.opening;this.closedByUs=!1;let e=this.opts.webSocket??globalThis.WebSocket,t=`${this.opts.baseUrl.replace(/^http/,"ws")}/realtime/v1/websocket?vsn=2.0.0&apikey=${encodeURIComponent(this.opts.publishableKey)}`,s=new Promise((i,r)=>{let a=new e(t);this.ws=a,a.onopen=()=>{this.attempts=0,this.startHeartbeat(),i()},a.onmessage=o=>this.receive(String(o.data)),a.onerror=()=>r(new Error("the realtime socket could not be opened")),a.onclose=()=>this.dropped(a)});return this.opening=s,s.finally(()=>{this.opening===s&&(this.opening=null)}).catch(()=>{}),s}dropped(e){if(this.ws!==e)return;this.ws=null,this.stopHeartbeat();for(let[,i]of this.replies)i("error",{reason:"socket closed"});this.replies.clear();for(let i of this.channels.values())i.state==="joined"&&(i.state="joining");if(this.closedByUs||![...this.channels.values()].some(i=>i.state==="joining"))return;let t=this.opts.backoff??[500,1e3,2e3,5e3,1e4,3e4],s=t[Math.min(this.attempts,t.length-1)]*(.8+Math.random()*.4);this.attempts++,this.reconnectTimer=S(setTimeout(()=>{this.reconnectTimer=null,this.reconnects++;let i=[...this.channels.values()].filter(r=>r.state==="joining");this.open().then(()=>Promise.all(i.map(r=>this.sendJoin(r)))).catch(()=>{})},s))}async sendJoin(e){let t=await this.opts.accessToken(),s=this.nextRef();e.joinRef=s;let i=this.awaitReply(s),r={broadcast:{self:!1}};e.logSeq!==void 0&&(r.log={after:e.logSeq}),this.push(s,e.topic,"phx_join",{access_token:t,config:r},s);let{status:a,response:o}=await i;if(this.channels.get(e.name)!==e||e.state!=="joining"){a==="ok"&&this.ws?.readyState===1&&this.push(s,e.topic,"phx_leave",{});return}e.settled(a==="ok",o)}awaitReply(e){return new Promise(t=>{let s=S(setTimeout(()=>{this.replies.delete(e),t({status:"timeout",response:{reason:"no reply"}})},this.opts.replyTimeoutMs??1e4));this.replies.set(e,(i,r)=>{clearTimeout(s),this.replies.delete(e),t({status:i,response:r})})})}receive(e){let t;try{t=JSON.parse(e)}catch{return}if(!Array.isArray(t)||t.length!==5||typeof t[2]!="string"||typeof t[3]!="string")return;let[,s,i,r,a]=t,o=typeof a=="object"&&a!==null?a:{};if(r==="phx_reply"&&typeof s=="string"){if(s===this.heartbeatRef){this.heartbeatRef=null;return}this.replies.get(s)?.(String(o.status),o.response??{});return}let l=[...this.channels.values()].find(c=>c.topic===i);if(l){if(r==="phx_close"){l.state="closed";return}if(r==="phx_error"){l.state="joining",S(setTimeout(()=>this.channels.get(l.name)===l&&l.state==="joining"&&this.ws?.readyState===1&&void this.sendJoin(l),1e3));return}l.deliver(r,o)}}push(e,t,s,i,r=this.nextRef()){this.ws?.send(JSON.stringify([e,r,t,s,i]))}nextRef(){return String(++this.ref)}startHeartbeat(){this.stopHeartbeat(),this.heartbeat=S(setInterval(()=>{if(this.heartbeatRef!==null){this.heartbeatRef=null,this.ws?.close(4e3);return}this.heartbeatRef=this.nextRef(),this.push(null,"phoenix","heartbeat",{},this.heartbeatRef)},this.opts.heartbeatMs??25e3))}stopHeartbeat(){this.heartbeat&&clearInterval(this.heartbeat),this.heartbeat=null,this.heartbeatRef=null}};function S(n){return n.unref?.(),n}var ie=30*6e4,V=1e3,re=100,oe=60*1024,T=()=>globalThis.crypto?.randomUUID?globalThis.crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;function P(){try{return globalThis.localStorage}catch{return}}var R=class{api;closingApi;flushMs;flushAt;now;anonKey;sessionKey;events=[];samples=[];timer;memoryAnon;memorySession;appVersion;stopAuto;session;identifyPending=!1;constructor(e,t,s,i={}){this.session=t,this.api=O.client(e),this.closingApi=O.client({send:r=>e.send({...r,keepalive:!0})}),this.flushMs=i.flushMs??5e3,this.flushAt=i.flushAt??20,this.now=i.now??Date.now,this.anonKey=`palbase.analytics.anonymous.${s}`,this.sessionKey=`palbase.analytics.session.${s}`,t.onChange(r=>{r==="SIGNED_IN"?(this.identifyPending=!0,this.soon()):r==="SIGNED_OUT"&&(this.identifyPending=!1,this.reset())})}capture(e,t){let s=this.now();this.hold({event:e,distinct_id:this.distinctId(),properties:t,timestamp:s,session_id:this.sessionId(s),...this.appVersion?{app_version:this.appVersion}:{}})}page(e={}){let t=globalThis.location,s=globalThis.document,i=e.url??(t?`${t.origin}${t.pathname}`:"");if(!i)return;let r=e.referrer??ue(s?.referrer);this.capture("$pageview",{...e.properties,url:i,...e.title??s?.title?{title:e.title??s?.title}:{},...r?{referrer:r}:{}})}async identify(e){this.identifyPending=!1;let t=this.session.snapshot()?.userId;t&&(await this.flush(),await this.sendIdentify(this.api,t,e))}async sendIdentify(e,t,s){try{await e.identify({body:{distinct_id:t,anonymous_id:this.anonymousId(),traits:s,timestamp:this.now(),sent_at:this.now()}})}catch{}}reset(){this.memoryAnon=void 0,this.memorySession=void 0;try{P()?.removeItem(this.anonKey),P()?.removeItem(this.sessionKey)}catch{}}async flush(e=!1){this.timer&&clearTimeout(this.timer),this.timer=void 0;let t=e?this.closingApi:this.api,s=this.now(),i=this.session.snapshot()?.userId;for(this.identifyPending&&i&&(this.identifyPending=!1,await this.sendIdentify(t,i));this.events.length>0;){let r=this.take(this.events,e);try{await t.batch({body:{events:r.map(a=>({...a,sent_at:s}))}})}catch{}}for(;this.samples.length>0;){let r=this.take(this.samples,e);try{await t.sendPerf({body:{items:r}})}catch{}}}start(e={}){this.stopAuto?.(),this.appVersion=e.appVersion;let t=globalThis;if(typeof t.addEventListener!="function"||typeof globalThis.document>"u")return()=>{};let s=[];e.pageViews!==!1&&s.push(this.watchPages(t)),e.webVitals!==!1&&s.push(le((a,o)=>this.sample("web_vital",a,o))),e.network!==!1&&s.push(this.watchFetch(t));let i=()=>{document.visibilityState==="hidden"&&this.flush(!0)},r=()=>{this.flush(!0)};return document.addEventListener("visibilitychange",i),t.addEventListener("pagehide",r),s.push(()=>{document.removeEventListener("visibilitychange",i),t.removeEventListener("pagehide",r)}),this.stopAuto=()=>{for(let a of s.splice(0))a();this.stopAuto=void 0},this.stopAuto}hold(e){this.events.length>=V&&this.events.shift(),this.events.push(e),this.soon()}sample(e,t,s,i={}){this.samples.length>=V&&this.samples.shift(),this.samples.push({row_id:T(),trace_type:e,name:t,value:Math.max(0,s),timestamp:this.now(),distinct_id:this.distinctId(),...i}),this.soon()}soon(){if(this.events.length+this.samples.length>=this.flushAt){this.flush();return}this.timer||(this.timer=setTimeout(()=>{this.flush()},this.flushMs),this.timer.unref?.())}take(e,t){let s=e.splice(0,re);if(!t)return s;for(;s.length>1&&JSON.stringify(s).length>oe;)e.unshift(s.pop());return s}distinctId(){return this.session.snapshot()?.userId??this.anonymousId()}anonymousId(){let e=P();try{let t=e?.getItem(this.anonKey);if(t)return t;let s=`anon_${T()}`;if(e?.setItem(this.anonKey,s),e?.getItem(this.anonKey)===s)return s}catch{}return this.memoryAnon??=`anon_${T()}`,this.memoryAnon}sessionId(e){let t=P(),s=this.memorySession;try{let r=t?.getItem(this.sessionKey);r&&(s=JSON.parse(r))}catch{}let i=s&&e-s.last<ie?{id:s.id,last:e}:{id:`ses_${T()}`,last:e};this.memorySession=i;try{t?.setItem(this.sessionKey,JSON.stringify(i))}catch{}return i.id}watchPages(e){let t="",s=()=>{let o=`${e.location.origin}${e.location.pathname}`;o!==t&&(t=o,setTimeout(()=>this.page({url:o}),0))},i=e.history,r=i.pushState.bind(i),a=i.replaceState.bind(i);return i.pushState=(...o)=>{r(...o),s()},i.replaceState=(...o)=>{a(...o),s()},e.addEventListener("popstate",s),s(),()=>{i.pushState=r,i.replaceState=a,e.removeEventListener("popstate",s)}}watchFetch(e){let t=e.fetch;if(typeof t!="function")return()=>{};let s=async(i,r)=>{let a=performance.now(),o=i instanceof Request?i:void 0,l=(r?.method??o?.method??"GET").toUpperCase(),c;try{c=new URL(o?.url??String(i),e.location.href)}catch{c=void 0}let h=!!c&&c.pathname.startsWith("/analytics/")||he(r?.headers??o?.headers);try{let d=await t(i,r);return c&&!h&&this.sample("network",`${l} ${c.origin}${c.pathname}`,performance.now()-a,{attrs:{status:String(d.status)},...d.headers.get("x-request-id")?{request_id:d.headers.get("x-request-id")}:{}}),d}catch(d){throw c&&!h&&this.sample("network",`${l} ${c.origin}${c.pathname}`,performance.now()-a,{attrs:{status:"0"}}),d}};return e.fetch=s,()=>{e.fetch===s&&(e.fetch=t)}}},ae=n=>n.entryType==="layout-shift"&&"value"in n&&"hadRecentInput"in n,ce=n=>n.entryType==="event"||n.entryType==="first-input";function I(n,e){if(!(typeof PerformanceObserver>"u"))try{let t=new PerformanceObserver(s=>e(s.getEntries()));return t.observe({type:n,buffered:!0}),t}catch{return}}function le(n){if(typeof document>"u"||typeof performance>"u"||typeof performance.getEntriesByType!="function")return()=>{};let e=0,t=0,s=0,i=[I("largest-contentful-paint",c=>{for(let h of c)e=Math.max(e,h.startTime)}),I("layout-shift",c=>{for(let h of c)ae(h)&&!h.hadRecentInput&&(t+=h.value)}),I("event",c=>{for(let h of c)ce(h)&&(s=Math.max(s,h.duration))}),I("paint",c=>{for(let h of c)h.name==="first-contentful-paint"&&n("FCP",h.startTime)})],r=performance.getEntriesByType("navigation")[0];r&&r.responseStart>0&&n("TTFB",r.responseStart);let a=!1,o=()=>{a||(a=!0,e>0&&n("LCP",e),n("CLS",t),s>0&&n("INP",s))},l=()=>{document.visibilityState==="hidden"&&o()};return document.addEventListener("visibilitychange",l),()=>{o(),document.removeEventListener("visibilitychange",l);for(let c of i)c?.disconnect()}}function he(n){if(!n)return!1;try{return new Headers(n).has("next-router-prefetch")}catch{return!1}}function ue(n){if(!n)return"";try{let e=new URL(n);return`${e.origin}${e.pathname}`}catch{return n.split(/[?#]/,1)[0]??""}}var E=class{api;session;socket;ref;fetch;appVersion;app;listeners=new Set;watch;following;last;constructor(e,t,s,i,r,a,o){this.api=j.client(e),this.session=t,this.socket=s,this.ref=i,this.fetch=r,this.appVersion=a,this.app=o}async me(){let e=this.session.snapshot()?.userId,t=(await this.api.getMyCustomerInfo()).data;return this.watch&&this.watch.userId===e&&this.offer(this.watch,t),t}async offerings(e="web"){return(await this.api.getOfferings({query:{channel:e}})).data}async paywall(e,t={}){return F(this.api,this.ref,e,s=>this.checkout(s),{signal:t.signal,fetch:this.fetch,attributes:t.attributes,locale:t.locale,appVersion:this.appVersion,app:this.app})}async checkout(e){let t={product:e.product,plan:e.plan,success_url:e.successUrl,cancel_url:e.cancelUrl};return(await this.api.createCheckout({body:t})).data.url}async portal(e){return(await this.api.createPortalSession({body:{return_url:e}})).data.url}async mergeGrant(){return(await this.api.createMergeGrant()).data}async merge(e){let t=this.session.snapshot()?.userId,s=(await this.api.mergeGuest({body:{grant:typeof e=="string"?e:e.grant}})).data;return this.watch&&this.watch.userId===t&&this.offer(this.watch,s),s}onCustomerInfo(e){if(this.listeners.add(e),this.listeners.size===1)this.start();else if(this.last!==void 0){let t=this.last;queueMicrotask(()=>this.listeners.has(e)&&B(e,t))}return()=>{this.listeners.delete(e)&&this.listeners.size===0&&this.stop()}}start(){this.following=this.session.onChange((e,t)=>{(e==="SIGNED_IN"||e==="SIGNED_OUT"||e==="SESSION_EXPIRED")&&this.follow(t)}),this.session.session().then(e=>{this.following&&!this.watch&&this.last===void 0&&this.follow(e)})}stop(){this.following?.(),this.following=void 0,this.unfollow(),this.last=void 0}follow(e){if(this.unfollow(),!e){this.tell(null);return}let t=this.socket.channel(`purchases:${this.ref}:${e.userId}`),s={userId:e.userId,channel:t,off:()=>{},revision:-1,reading:!1,wanted:Number.NEGATIVE_INFINITY},i=[t.on("broadcast",r=>{r.event==="changed"&&this.changed(s,N(r.payload))}),t.on("state_snapshot",r=>this.held(s,N(G(r.entries)))),t.on("state_diff",r=>this.held(s,N(G(r.set))))];s.off=()=>i.forEach(r=>r()),this.watch=s,t.join().catch(()=>{}),this.read(s)}unfollow(){let e=this.watch;e&&(this.watch=void 0,e.off(),e.channel.leave())}changed(e,t){this.watch!==e||t!==void 0&&t<=e.revision||(e.reading?e.wanted=Math.max(e.wanted,t??Number.POSITIVE_INFINITY):this.read(e))}held(e,t){t!==void 0&&this.changed(e,t)}async read(e){e.reading=!0;try{for(;;){e.wanted=Number.NEGATIVE_INFINITY;try{let t=(await this.api.getMyCustomerInfo()).data;if(this.watch!==e)return;this.offer(e,t)}catch{}if(this.watch!==e||e.wanted<=e.revision)return}}finally{e.reading=!1}}offer(e,t){this.watch!==e||t.revision<=e.revision||(e.revision=t.revision,this.tell(t))}tell(e){if(!(this.last===null&&e===null)){this.last=e;for(let t of[...this.listeners])B(t,e)}}};function B(n,e){try{n(e)}catch{}}function G(n){let e=typeof n=="object"&&n!==null?n.revision:void 0;return typeof e=="object"&&e!==null?e.v:void 0}function N(n){let e=typeof n=="object"&&n!==null?n.revision:void 0;return typeof e=="number"&&Number.isFinite(e)?e:void 0}var _=class{api;open;session;credentials;mfa;passkey;magicLink;phone;password;email;sessions;constructor(e,t,s,i){this.api=m.client(e),this.open=m.client(t),this.session=s,this.credentials=i;let r=this.api,a=this.open;this.mfa={challenge:async(o,l,c)=>this.signedIn((await a.mfaChallenge({body:{mfa_token:o,type:l,code:c}})).data),recovery:async(o,l)=>this.signedIn((await a.mfaRecovery({body:{mfa_token:o,code:l}})).data),sendSmsCode:async o=>{await a.mfaSmsChallenge({body:{mfa_token:o}})},sendEmailCode:async o=>{await a.mfaEmailChallenge({body:{mfa_token:o}})},enrollTotp:async()=>{let o=(await r.mfaEnroll({body:{type:"totp"}})).data;return{enrollmentId:o.enrollment_id,secret:o.secret,otpUrl:o.otp_url,qrCode:o.qr_code,recoveryCodes:o.recovery_codes}},verifyTotp:async o=>{await r.mfaVerifyEnrollment({body:{code:o}})},enrollSms:async o=>(await r.mfaSmsEnroll({body:{phone:o}})).data,verifySms:async o=>{await r.mfaSmsVerifyEnrollment({body:{code:o}})},enrollEmail:async()=>{await r.mfaEmailEnroll()},factors:async()=>(await r.mfaListFactors()).data,removeFactor:async(o,l)=>{await r.mfaRemoveFactor({path:{id:o},body:{current_password:l}})},regenerateRecoveryCodes:async()=>(await r.mfaRegenerateRecoveryCodes()).data},this.passkey={register:async o=>{let l=(await r.webauthnRegisterBegin({body:{name:o}})).data,c=await this.authenticator().create({publicKey:J(l.options.publicKey)});return(await r.webauthnRegisterFinish({body:X(c)})).data},signIn:async(o={})=>{let l=(await a.webauthnLoginBegin({body:o.userId?{user_id:o.userId}:{}})).data,c=await this.authenticator().get({publicKey:W(l.options.publicKey)}),h=await a.webauthnLoginFinish({query:o.userId?{user_id:o.userId}:void 0,body:Y(c)});return this.signedIn(h.data)},signUp:async(o,l)=>{let c=(await a.webauthnSignupBegin({body:{identifier:o,user_name:l}})).data,h=await this.authenticator().create({publicKey:J(c.options.publicKey)});await a.webauthnSignupFinish({query:{user_id:c.user_id},body:X(h)});let d=(await a.webauthnLoginBegin({body:{user_id:c.user_id}})).data,y=await this.authenticator().get({publicKey:W(d.options.publicKey)});return this.signedIn((await a.webauthnLoginFinish({query:{user_id:c.user_id},body:Y(y)})).data)},list:async()=>(await r.webauthnListCredentials()).data,remove:async o=>{await r.webauthnDeleteCredential({path:{id:o}})}},this.magicLink={send:async o=>{await a.magicLinkRequest({body:{email:o}})},verify:async o=>this.signedIn((await a.magicLinkVerify({body:{token:o}})).data)},this.phone={sendCode:async o=>(await a.requestPhoneOtp({body:{phone:o,channel:"sms"}})).data,verifyCode:async(o,l)=>this.signInResult((await a.verifyPhoneOtp({body:{phone:o,token:l}})).data)},this.password={requestReset:async o=>{await a.passwordResetRequest({body:{email:o}})},confirmReset:async(o,l)=>{await a.passwordResetConfirm({body:{token:o,new_password:l}})},change:async o=>(await r.passwordChange({body:o})).data,set:async o=>(await r.setPassword({body:o})).data},this.email={verify:async o=>{await a.verifyEmail({body:o})},resendVerification:async o=>(await a.resendVerification({body:{email:o}})).data},this.sessions={list:async()=>(await r.listSessions()).data,revoke:async o=>{await r.revokeSession({path:{id:o}})},revokeAll:async()=>{await r.revokeAllSessions()}}}onAuthStateChange(e){return this.session.onChange(e)}currentSession(){return this.session.session()}async signUp(e){let t=(await this.open.signup({body:e})).data;return t.access_token&&t.refresh_token&&t.expires_in!==void 0&&await this.session.set({access_token:t.access_token,refresh_token:t.refresh_token,expires_in:t.expires_in},"SIGNED_IN",t.user),t}async signIn(e,t){return this.signInResult((await this.open.login({body:{email:e,password:t}})).data)}async signOut(){try{await this.session.session()&&await this.api.logout()}finally{await this.session.clear("SIGNED_OUT")}}async getUser(){let e=this.session.snapshot(),t=(await this.api.getMe()).data;return await this.session.setUser(t,e),t}async refreshSession(){return this.session.refresh()}async deleteAccount(e){await this.api.deleteAccount(e===void 0?void 0:{body:{password:e}}),await this.session.clear("SIGNED_OUT")}async signInResult(e){return"mfa_required"in e&&e.mfa_required?{mfaRequired:{mfaToken:e.mfa_token,factors:e.factors}}:{user:await this.signedIn(e)}}async setSession(e){return this.signedIn(e)}async signedIn(e){let t=await this.session.set({access_token:e.access_token,refresh_token:e.refresh_token,expires_in:e.expires_in},"SIGNED_IN",e.user);if(e.user)return e.user;let s=(await this.api.verifyTokenUser()).data;return await this.session.setUser(s,t),s}authenticator(){if(!this.credentials)throw new Error("passkeys need navigator.credentials (or createClient({ webauthn }))");return this.credentials}},C=n=>{let e=atob(n.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(n.length/4)*4,"=")),t=new Uint8Array(e.length);for(let s=0;s<e.length;s++)t[s]=e.charCodeAt(s);return t.buffer},f=n=>{if(!n)return;let e=n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n.buffer,n.byteOffset,n.byteLength),t="";for(let s of e)t+=String.fromCharCode(s);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")},z=n=>Array.isArray(n)?n.map(e=>({...e,id:C(String(e.id))})):void 0;function J(n){let e=n.user;return{...n,challenge:C(String(n.challenge)),user:{...e,id:C(String(e.id))},excludeCredentials:z(n.excludeCredentials)}}function W(n){return{...n,challenge:C(String(n.challenge)),allowCredentials:z(n.allowCredentials)}}function X(n){let e=n,t=e.response;return{id:e.id,rawId:f(e.rawId),type:"public-key",authenticatorAttachment:e.authenticatorAttachment??void 0,clientExtensionResults:e.getClientExtensionResults?.()??{},response:{clientDataJSON:f(t.clientDataJSON),attestationObject:f(t.attestationObject),transports:t.getTransports?.()}}}function Y(n){let e=n,t=e.response;return{id:e.id,rawId:f(e.rawId),type:"public-key",authenticatorAttachment:e.authenticatorAttachment??void 0,clientExtensionResults:e.getClientExtensionResults?.()??{},response:{clientDataJSON:f(t.clientDataJSON),authenticatorData:f(t.authenticatorData),signature:f(t.signature),userHandle:f(t.userHandle)}}}var de=/^pb_[a-z0-9]+_s/,fe=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,pe=new Set(["invalid_token","token_expired","session_expired","session_revoked","missing_token"]),me=new Set(["invalid_token","token_reuse","token_expired","session_revoked","session_expired","session_device_changed"]);function ye(n){return n.status===400&&n.code==="invalid_request"}function Ne(n){if(de.test(n.publishableKey)||"secretKey"in n)throw new TypeError("a secret key never goes into a client app: palbase/web takes the publishable key (pb_<ref>_c\u2026); servers use palbase/server");let e=/^pb_([a-z0-9]+)_c/.exec(n.publishableKey)?.[1];if(e===void 0)throw new TypeError("publishableKey is the project publishable key (pb_<ref>_c\u2026)");if(n.appId!==void 0&&!fe.test(n.appId))throw new TypeError("appId is the registered app's id, a UUID (settings.appId of palbase apps:sync), not its slug");let t=new URL(n.url).origin,s=n.storage??(ge()?H(`palbase.session.${t}`):$()),i=n.retry??q,r=w(v({baseUrl:n.url,credentials:{publishableKey:n.publishableKey},fetch:n.fetch}),i),a=m.client(r),o=new b(s,async u=>(await a.refreshToken({body:{refresh_token:u}})).data,u=>u instanceof x&&(u.status===401&&me.has(u.code)||ye(u)),{lockName:`palbase.session.refresh.${t}`,refresh:n.refresh}),l=v({baseUrl:n.url,credentials:{publishableKey:n.publishableKey,accessToken:()=>o.accessToken()},fetch:n.fetch,xmlHttpRequest:n.xmlHttpRequest??(n.fetch===void 0?globalThis.XMLHttpRequest:void 0)}),c=w(L(l,o),i),h=new k({baseUrl:n.url,publishableKey:n.publishableKey,accessToken:()=>o.accessToken(),webSocket:n.webSocket});o.onChange(u=>{u==="SIGNED_OUT"||u==="SESSION_EXPIRED"||u==="SIGNED_IN"?h.disconnect():u==="TOKEN_REFRESHED"&&h.tokenChanged()});let d=new R(L(l,o),o,t);n.analytics&&d.start(n.analytics===!0?{}:n.analytics);let y=new Map,Q=u=>{let A=new URL(u).origin,g=y.get(A);return g||(g=w(L(v({baseUrl:A,credentials:{accessToken:()=>o.accessToken()},fetch:n.fetch}),o),i),y.set(A,g)),g},U=Object.assign(Object.create(D),{auth:new _(c,r,o,n.webauthn??globalThis.navigator?.credentials),socket:h,transport:c,analytics:d,purchases:new E(c,o,h,e,n.fetch,n.appVersion,n.appId)});return K(U,t,Q,c,h,()=>o.snapshot()?.userId),U}function ge(){try{return typeof globalThis.localStorage<"u"}catch{return!1}}function L(n,e){return{async send(t){let s=await e.session();try{return await n.send(t)}catch(i){let r=t.auth.some(a=>a.includes("userJWT"));if(!(i instanceof x)||i.status!==401||!pe.has(i.code)||!r||!s)throw i;try{e.snapshot()?.refreshToken===s.refreshToken&&await e.refresh()}catch{throw i}if(e.snapshot()?.userId!==s.userId)throw i;return n.send(t)}}}}export{p as a,$ as b,H as c,M as d,k as e,R as f,E as g,Ne as h};