@marktiderman/genesis-chat 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +163 -2
- package/dist/chunk-27UTR5FW.js +322 -0
- package/dist/chunk-27UTR5FW.js.map +1 -0
- package/dist/chunk-JQXZUEEL.js +525 -0
- package/dist/chunk-JQXZUEEL.js.map +1 -0
- package/dist/chunk-SAY24E6V.js +309 -0
- package/dist/chunk-SAY24E6V.js.map +1 -0
- package/dist/client-BtdALFCx.d.ts +349 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +2 -0
- package/dist/native.js +3 -0
- package/dist/native.js.map +1 -0
- package/dist/react.d.ts +137 -0
- package/dist/react.js +5 -0
- package/dist/react.js.map +1 -0
- package/dist/server.d.ts +311 -0
- package/dist/server.js +562 -0
- package/dist/server.js.map +1 -0
- package/dist/web.d.ts +387 -0
- package/dist/web.js +2405 -0
- package/dist/web.js.map +1 -0
- package/migrations/0001_genesis_chat.sql +1836 -0
- package/migrations/tests/0001_genesis_chat.test.sql +254 -0
- package/package.json +144 -3
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
2
|
+
import { C as ChatClient, a as ChatConversation, b as ChatMember, c as ChatRole, d as ChatKindRules, e as ChatMessage, f as ChatActivity, S as SendInput, g as ChatSignal, h as ChatInboxItem } from './client-BtdALFCx.js';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
import { OutboxStore, LocalFile, ThreadItem, Outbox, QueuedMessage } from './index.js';
|
|
6
|
+
import '@supabase/supabase-js';
|
|
7
|
+
|
|
8
|
+
declare const chatKeys: {
|
|
9
|
+
all: readonly ["genesis-chat"];
|
|
10
|
+
inbox: (opts?: object) => readonly ["genesis-chat", "inbox", object];
|
|
11
|
+
unread: () => readonly ["genesis-chat", "unread"];
|
|
12
|
+
conversation: (id: string) => readonly ["genesis-chat", "conversation", string];
|
|
13
|
+
members: (id: string) => readonly ["genesis-chat", "members", string];
|
|
14
|
+
role: (id: string) => readonly ["genesis-chat", "role", string];
|
|
15
|
+
pins: (id: string) => readonly ["genesis-chat", "pins", string];
|
|
16
|
+
files: (paths: string[]) => readonly ["genesis-chat", "files", string[]];
|
|
17
|
+
};
|
|
18
|
+
/** Does a messages list (`[genesis-chat, conversation, id, messages, threadRootId, topic]`) hold this message? */
|
|
19
|
+
declare function belongsTo(key: readonly unknown[], m: ChatMessage): boolean;
|
|
20
|
+
interface ChatContextValue {
|
|
21
|
+
client: ChatClient;
|
|
22
|
+
userId: string;
|
|
23
|
+
outbox: Outbox;
|
|
24
|
+
queue: QueuedMessage[];
|
|
25
|
+
loadPeople?: (userIds: string[]) => Promise<Record<string, ChatPerson>>;
|
|
26
|
+
/** Subscribe to inbox hints (one channel for the app, fanned out here). */
|
|
27
|
+
onInbox(listener: (s: ChatSignal) => void): () => void;
|
|
28
|
+
}
|
|
29
|
+
interface ChatProviderProps {
|
|
30
|
+
client: ChatClient;
|
|
31
|
+
/** The signed-in user's id (auth.users). */
|
|
32
|
+
userId: string;
|
|
33
|
+
/** Where unsent messages wait between launches (localStorage, MMKV…). Memory by default. */
|
|
34
|
+
outboxStore?: OutboxStore;
|
|
35
|
+
/** Reads a native file URI for upload. */
|
|
36
|
+
readFile?: (uri: string) => Promise<Blob | ArrayBuffer>;
|
|
37
|
+
/** Replaces the plain upload (resumable uploads on native). */
|
|
38
|
+
upload?: (conversationId: string, file: LocalFile) => Promise<string>;
|
|
39
|
+
/** Makes client ids (default: a random UUID that works without crypto.randomUUID). */
|
|
40
|
+
newId?: () => string;
|
|
41
|
+
/** Keeps a byte file (a pasted image, a recording) across launches: write it, return a URI. */
|
|
42
|
+
persistFile?: (file: LocalFile) => Promise<string>;
|
|
43
|
+
/** Names and avatars for user ids, fetched in batches (usePeople, usePerson). */
|
|
44
|
+
loadPeople?: (userIds: string[]) => Promise<Record<string, ChatPerson>>;
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
}
|
|
47
|
+
/** How the app shows a person. */
|
|
48
|
+
interface ChatPerson {
|
|
49
|
+
name: string;
|
|
50
|
+
avatarUrl?: string | null;
|
|
51
|
+
}
|
|
52
|
+
/** Puts chat in reach of every hook below it. Needs a TanStack QueryClientProvider above it. */
|
|
53
|
+
declare function ChatProvider({ client, userId, outboxStore, readFile, upload, newId, persistFile, loadPeople, children }: ChatProviderProps): react.JSX.Element;
|
|
54
|
+
declare function useChat(): ChatContextValue;
|
|
55
|
+
/** Your conversations, live. `kind` narrows to one kind; `archived` shows the archived ones. */
|
|
56
|
+
declare function useInbox(opts?: {
|
|
57
|
+
kind?: string;
|
|
58
|
+
archived?: boolean;
|
|
59
|
+
limit?: number;
|
|
60
|
+
}): {
|
|
61
|
+
items: NoInfer<ChatInboxItem[]>;
|
|
62
|
+
isLoading: boolean;
|
|
63
|
+
error: Error | null;
|
|
64
|
+
refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<NoInfer<ChatInboxItem[]>, Error>>;
|
|
65
|
+
};
|
|
66
|
+
/** How many messages wait for you everywhere (for a badge), live. */
|
|
67
|
+
declare function useUnreadTotal(): number;
|
|
68
|
+
interface UseConversationOptions {
|
|
69
|
+
/** Mark messages read as they arrive while this is true (the thread is on screen). Default true. */
|
|
70
|
+
markRead?: boolean;
|
|
71
|
+
/** One thread's replies instead of the main timeline. */
|
|
72
|
+
threadRootId?: string;
|
|
73
|
+
/** One topic's messages. */
|
|
74
|
+
topic?: string;
|
|
75
|
+
pageSize?: number;
|
|
76
|
+
}
|
|
77
|
+
/** One conversation: its messages (with what you are still sending), members, your role, and who is typing. */
|
|
78
|
+
declare function useConversation(conversationId: string | null | undefined, opts?: UseConversationOptions): {
|
|
79
|
+
conversation: NonNullable<NoInfer<ChatConversation | null>> | null;
|
|
80
|
+
members: NoInfer<ChatMember[]>;
|
|
81
|
+
role: ChatRole | null;
|
|
82
|
+
canPost: boolean;
|
|
83
|
+
/** Your role and posting rights are still loading: show the composer as waiting, not refused. */
|
|
84
|
+
accessLoading: boolean;
|
|
85
|
+
/** The kind's rules (max members, threads, who posts). */
|
|
86
|
+
kind: NonNullable<NoInfer<ChatKindRules | null>> | null;
|
|
87
|
+
/** May you pin here: owners and admins, or anyone in a two-person conversation. */
|
|
88
|
+
canPin: boolean;
|
|
89
|
+
/** Gives a message whose files were lost on a relaunch its files again. */
|
|
90
|
+
reattach: (clientId: string, files: LocalFile[]) => Promise<void>;
|
|
91
|
+
items: ThreadItem[];
|
|
92
|
+
messages: ChatMessage[];
|
|
93
|
+
isLoading: boolean;
|
|
94
|
+
error: Error | null;
|
|
95
|
+
hasEarlier: boolean;
|
|
96
|
+
loadingEarlier: boolean;
|
|
97
|
+
loadEarlier: () => Promise<void>;
|
|
98
|
+
/** Where to draw the "New" line, from what you had read when it opened. */
|
|
99
|
+
firstUnreadId: string | null;
|
|
100
|
+
activity: ChatActivity[];
|
|
101
|
+
signalTyping: () => void;
|
|
102
|
+
signalRecording: () => void;
|
|
103
|
+
markRead: () => Promise<void>;
|
|
104
|
+
send: (input: Omit<SendInput, "conversationId" | "clientId">, files?: LocalFile[]) => Promise<string>;
|
|
105
|
+
retry: (clientId: string) => Promise<void>;
|
|
106
|
+
discard: (clientId: string) => Promise<void>;
|
|
107
|
+
refetch: () => Promise<void>;
|
|
108
|
+
};
|
|
109
|
+
type ConversationState = ReturnType<typeof useConversation>;
|
|
110
|
+
/** Edit, delete, react, pin, save and report, refreshing what they change. */
|
|
111
|
+
declare function useMessageActions(): {
|
|
112
|
+
edit: (m: ChatMessage, body: string, mentions?: string[]) => Promise<void>;
|
|
113
|
+
remove: (m: ChatMessage) => Promise<void>;
|
|
114
|
+
react: (m: ChatMessage, emoji: string, on: boolean) => Promise<void>;
|
|
115
|
+
pin: (m: ChatMessage, on: boolean) => Promise<void>;
|
|
116
|
+
save: (m: ChatMessage, on: boolean, note?: string) => Promise<void>;
|
|
117
|
+
report: (m: ChatMessage, reason: "spam" | "harassment" | "safety" | "other", note?: string) => Promise<string>;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Names and avatars for these people, from the provider's `loadPeople`, fetched as one
|
|
121
|
+
* batch per set of ids and cached. Unknown ids are left out.
|
|
122
|
+
*/
|
|
123
|
+
declare function usePeople(userIds: (string | null | undefined)[]): Record<string, ChatPerson>;
|
|
124
|
+
/** One person's name and avatar (see usePeople). */
|
|
125
|
+
declare function usePerson(userId: string | null | undefined): ChatPerson | null;
|
|
126
|
+
/** The pinned message ids of a conversation. */
|
|
127
|
+
declare function usePins(conversationId: string | null | undefined): string[];
|
|
128
|
+
/** Signed links for files (cached by the client until shortly before they expire). */
|
|
129
|
+
declare function useFileUrls(paths: (string | null | undefined)[]): Record<string, string>;
|
|
130
|
+
/** Starts (or finds again) a conversation, then refreshes the inbox. */
|
|
131
|
+
declare function useStartConversation(): (kind: string, memberIds: string[], opts?: {
|
|
132
|
+
title?: string;
|
|
133
|
+
subjectType?: string;
|
|
134
|
+
subjectId?: string;
|
|
135
|
+
}) => Promise<string>;
|
|
136
|
+
|
|
137
|
+
export { type ChatPerson, ChatProvider, type ChatProviderProps, type ConversationState, LocalFile, QueuedMessage, ThreadItem, type UseConversationOptions, belongsTo, chatKeys, useChat, useConversation, useFileUrls, useInbox, useMessageActions, usePeople, usePerson, usePins, useStartConversation, useUnreadTotal };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ChatProvider, belongsTo, chatKeys, useChat, useConversation, useFileUrls, useInbox, useMessageActions, usePeople, usePerson, usePins, useStartConversation, useUnreadTotal } from './chunk-27UTR5FW.js';
|
|
2
|
+
import './chunk-SAY24E6V.js';
|
|
3
|
+
import './chunk-JQXZUEEL.js';
|
|
4
|
+
//# sourceMappingURL=react.js.map
|
|
5
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"react.js"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { i as ChatSupabase } from './client-BtdALFCx.js';
|
|
2
|
+
import '@supabase/supabase-js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The server's contracts: what the runner hands an app's push sender, and the
|
|
6
|
+
* adapters it takes for transcripts and hosted video. No React, no Node APIs:
|
|
7
|
+
* fetch and Web Crypto only, so it runs in Node, edge functions and Deno.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** One person a chat push goes to. */
|
|
11
|
+
interface ChatRecipient {
|
|
12
|
+
userId: string;
|
|
13
|
+
/** Their unread total across conversations (capped at 99 each), for the app badge. */
|
|
14
|
+
badge: number;
|
|
15
|
+
/** The message mentions them. */
|
|
16
|
+
mentioned: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One message's push, for every recipient at once. Its shape matches what
|
|
20
|
+
* `notify()` from genesis-notifications takes (type/category, recipients, data,
|
|
21
|
+
* idempotencyKey, threadKey, collapseKey), so an app can hand it on unchanged.
|
|
22
|
+
*/
|
|
23
|
+
interface ChatNotification {
|
|
24
|
+
category: "messages";
|
|
25
|
+
messageId: string;
|
|
26
|
+
conversationId: string;
|
|
27
|
+
recipients: ChatRecipient[];
|
|
28
|
+
/** The conversation's title, or the sender's name in a direct conversation. */
|
|
29
|
+
title: string;
|
|
30
|
+
/** The preview; in a group, prefixed with the sender's name ("Ana: see you"). */
|
|
31
|
+
body: string;
|
|
32
|
+
senderId: string | null;
|
|
33
|
+
/** `chat:<conversation id>`: a newer push replaces an older one where the platform can. */
|
|
34
|
+
collapseKey: string;
|
|
35
|
+
/** The conversation id: groups a conversation's pushes on the lock screen. */
|
|
36
|
+
threadKey: string;
|
|
37
|
+
/** `push:<message id>`: the same message is never pushed twice by a sender that dedupes. */
|
|
38
|
+
idempotencyKey: string;
|
|
39
|
+
data: {
|
|
40
|
+
url: string;
|
|
41
|
+
conversationId: string;
|
|
42
|
+
messageId: string;
|
|
43
|
+
seq: number | null;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Sends a message's push. Throw to have the effect retried (with backoff, up
|
|
48
|
+
* to 8 attempts); resolve once it is handed over. What it resolves with is
|
|
49
|
+
* passed back to nobody: keep what you need (tickets) yourself.
|
|
50
|
+
*/
|
|
51
|
+
type ChatDeliver = (notification: ChatNotification) => Promise<unknown>;
|
|
52
|
+
/** A voice note or video to transcribe. */
|
|
53
|
+
interface TranscribeInput {
|
|
54
|
+
/** A signed URL, valid for 10 minutes. */
|
|
55
|
+
url: string;
|
|
56
|
+
mime: string | null;
|
|
57
|
+
durationMs: number | null;
|
|
58
|
+
/** A BCP-47 code; without one the provider detects the language. */
|
|
59
|
+
language?: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
interface TranscribeResult {
|
|
62
|
+
text: string;
|
|
63
|
+
/** The language heard, when the provider says. */
|
|
64
|
+
language?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** A speech-to-text provider. Throw on failure: the effect is retried. */
|
|
67
|
+
interface Transcriber {
|
|
68
|
+
readonly name: string;
|
|
69
|
+
transcribe(input: TranscribeInput): Promise<TranscribeResult>;
|
|
70
|
+
}
|
|
71
|
+
/** A hosted video service (Mux): direct uploads, removal and its webhook. */
|
|
72
|
+
interface VideoProvider {
|
|
73
|
+
/** The attachments' `provider` value it owns (`mux`). */
|
|
74
|
+
readonly name: "mux" | "cloudflare";
|
|
75
|
+
/** A one-time URL the client uploads the file to. */
|
|
76
|
+
createUpload(options: {
|
|
77
|
+
corsOrigin: string;
|
|
78
|
+
passthrough?: string | undefined;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
uploadId: string;
|
|
81
|
+
url: string;
|
|
82
|
+
}>;
|
|
83
|
+
/** Removes a hosted asset. Already gone counts as removed. */
|
|
84
|
+
remove(assetId: string): Promise<void>;
|
|
85
|
+
/** Verifies and applies one of the service's webhooks. */
|
|
86
|
+
handleWebhook(request: Request, context: {
|
|
87
|
+
supabase: ChatSupabase;
|
|
88
|
+
}): Promise<Response>;
|
|
89
|
+
}
|
|
90
|
+
/** The outbox kinds the runner works. */
|
|
91
|
+
type ChatEffectKind = "push" | "transcribe" | "storage.remove" | "video.remove";
|
|
92
|
+
/** A claimed row of `genesis_chat.effects`. */
|
|
93
|
+
interface ChatEffect {
|
|
94
|
+
id: number;
|
|
95
|
+
kind: string;
|
|
96
|
+
key: string;
|
|
97
|
+
payload: Record<string, unknown>;
|
|
98
|
+
/** This attempt's number, from 1 (claim_effects counts it). */
|
|
99
|
+
attempts: number;
|
|
100
|
+
}
|
|
101
|
+
interface ChatRunReport {
|
|
102
|
+
claimed: number;
|
|
103
|
+
done: number;
|
|
104
|
+
/** Failed this time; retried later with backoff until the 8th attempt. */
|
|
105
|
+
failed: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The outbox runner: claims due effects from `genesis_chat.effects`, does
|
|
110
|
+
* each one, and settles it. Settling with an error has `complete_effect` back
|
|
111
|
+
* it off (30 s × 2^attempts); `claim_effects` gives up after the 8th attempt.
|
|
112
|
+
*
|
|
113
|
+
* push push_targets(message) → deliver(notification), once per message
|
|
114
|
+
* transcribe signed URL (10 min) → transcriber → update_attachment
|
|
115
|
+
* storage.remove storage.from(bucket).remove([path]); not found is done
|
|
116
|
+
* video.remove video.remove(provider id), when a video provider is given
|
|
117
|
+
*
|
|
118
|
+
* Kinds it doesn't handle are never claimed, so an app's own runner can work
|
|
119
|
+
* them. Effects run concurrently per kind, up to a cap; one failure never
|
|
120
|
+
* stops the others. Errors are stored as short messages, never payloads.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/** `claim_effects` stops claiming an effect after this many attempts. */
|
|
124
|
+
declare const CHAT_MAX_ATTEMPTS = 8;
|
|
125
|
+
interface ChatRunnerOptions {
|
|
126
|
+
/** A service-role supabase-js client (the runner reads past row level security). */
|
|
127
|
+
supabase: ChatSupabase;
|
|
128
|
+
/** Sends a message's push: `createExpoDeliver(...)`, or a function that calls genesis-notifications' `notify`. */
|
|
129
|
+
deliver: ChatDeliver;
|
|
130
|
+
/** Speech to text for voice notes and stored videos. Without one, their transcripts are set `off`. */
|
|
131
|
+
transcriber?: Transcriber | undefined;
|
|
132
|
+
/** The language to transcribe in (BCP-47). Default: detected. */
|
|
133
|
+
transcriptLanguage?: string | undefined;
|
|
134
|
+
/** Hosted video (Mux), to remove an asset when its attachment goes. */
|
|
135
|
+
video?: Pick<VideoProvider, "name" | "remove"> | undefined;
|
|
136
|
+
/** The kinds this runner works (default all four), so separate schedules can split them. */
|
|
137
|
+
kinds?: readonly ChatEffectKind[] | undefined;
|
|
138
|
+
batch?: {
|
|
139
|
+
/** Effects claimed per run (default 50, at most 500). */
|
|
140
|
+
limit?: number | undefined;
|
|
141
|
+
/** Effects of one kind in flight at once (default 4). */
|
|
142
|
+
concurrency?: number | undefined;
|
|
143
|
+
};
|
|
144
|
+
/** A person's display name, for push titles and group previews. */
|
|
145
|
+
resolveName?: ((userId: string) => Promise<string | null | undefined> | string | null | undefined) | undefined;
|
|
146
|
+
/** Kinds that are one-to-one (default `["direct"]`): their push title is the sender's name. */
|
|
147
|
+
directKinds?: readonly string[] | undefined;
|
|
148
|
+
/** Where a tapped notification opens (default `/messages/<conversationId>`). */
|
|
149
|
+
linkFor?: ((conversationId: string, messageId: string) => string) | undefined;
|
|
150
|
+
/** Safe log lines (effect ids, kinds, short errors). Default: nothing is logged. */
|
|
151
|
+
log?: ((line: string) => void) | undefined;
|
|
152
|
+
}
|
|
153
|
+
interface ChatRunner {
|
|
154
|
+
/** One claim, its work and its settling. Rejects only when claiming fails. */
|
|
155
|
+
run(): Promise<ChatRunReport>;
|
|
156
|
+
/** Works one effect (exported for tests and for apps that claim themselves). Throws to retry. */
|
|
157
|
+
handle(effect: ChatEffect): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
declare function createChatRunner(options: ChatRunnerOptions): ChatRunner;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Speech-to-text providers behind `Transcriber`. A failed call throws with
|
|
163
|
+
* the HTTP status only (an error body can echo the request), so the runner
|
|
164
|
+
* retries it.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
interface DeepgramOptions {
|
|
168
|
+
apiKey: string;
|
|
169
|
+
/** Default `nova-3`. */
|
|
170
|
+
model?: string | undefined;
|
|
171
|
+
fetch?: typeof fetch | undefined;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Deepgram pre-recorded transcription by URL
|
|
175
|
+
* (https://developers.deepgram.com/reference/speech-to-text/listen-pre-recorded):
|
|
176
|
+
* `POST /v1/listen?model=…&smart_format=true&detect_language=true` with
|
|
177
|
+
* `{ url }` and `Authorization: Token <key>`; Deepgram fetches the file itself.
|
|
178
|
+
*/
|
|
179
|
+
declare function deepgramTranscriber(options: DeepgramOptions): Transcriber;
|
|
180
|
+
interface OpenAiTranscriberOptions {
|
|
181
|
+
apiKey: string;
|
|
182
|
+
/** Default `gpt-4o-mini-transcribe`. */
|
|
183
|
+
model?: string | undefined;
|
|
184
|
+
fetch?: typeof fetch | undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* OpenAI `POST /v1/audio/transcriptions` (multipart: file, model, language):
|
|
188
|
+
* the file is downloaded from the signed URL first, as OpenAI takes no URL.
|
|
189
|
+
* Files over 25 MB are refused by OpenAI, so they fail here without the upload.
|
|
190
|
+
*/
|
|
191
|
+
declare function openAiTranscriber(options: OpenAiTranscriberOptions): Transcriber;
|
|
192
|
+
|
|
193
|
+
interface MuxSigningKey {
|
|
194
|
+
/** The signing key's id (`kid`). */
|
|
195
|
+
signingKeyId: string;
|
|
196
|
+
/** The private key as Mux gives it: base64 of a PEM. A PEM works too. */
|
|
197
|
+
privateKey: string;
|
|
198
|
+
}
|
|
199
|
+
interface MuxVideoOptions {
|
|
200
|
+
tokenId: string;
|
|
201
|
+
tokenSecret: string;
|
|
202
|
+
/** The webhook endpoint's signing secret (Settings → Webhooks). */
|
|
203
|
+
webhookSecret: string;
|
|
204
|
+
/** `public` (default) or `signed`; signed playback needs `signing` to build URLs. */
|
|
205
|
+
playbackPolicy?: "public" | "signed" | undefined;
|
|
206
|
+
/** Mux's auto-generated captions, kept as the transcript (default English; false turns them off). */
|
|
207
|
+
captions?: {
|
|
208
|
+
languageCode?: string;
|
|
209
|
+
name?: string;
|
|
210
|
+
} | false | undefined;
|
|
211
|
+
/** For signed playback: fetching generated captions as the transcript. */
|
|
212
|
+
signing?: MuxSigningKey | undefined;
|
|
213
|
+
/** Mux `video_quality` (default `basic`). */
|
|
214
|
+
videoQuality?: "basic" | "plus" | "premium" | undefined;
|
|
215
|
+
fetch?: typeof fetch | undefined;
|
|
216
|
+
now?: (() => number) | undefined;
|
|
217
|
+
}
|
|
218
|
+
/** Verifies a `mux-signature` header (`t=<unix>,v1=<hex>`) over the raw body. */
|
|
219
|
+
declare function verifyMuxSignature(header: string | null, body: string, secret: string, options?: {
|
|
220
|
+
now?: number;
|
|
221
|
+
toleranceSeconds?: number;
|
|
222
|
+
}): Promise<boolean>;
|
|
223
|
+
/**
|
|
224
|
+
* A signed playback URL (RS256 JWT with `sub` = playback id, `aud` = v, `kid`
|
|
225
|
+
* in the header), valid for `expiresInSeconds` (default 6 hours).
|
|
226
|
+
*/
|
|
227
|
+
declare function muxPlaybackUrl(playbackId: string, key: MuxSigningKey, options?: {
|
|
228
|
+
expiresInSeconds?: number;
|
|
229
|
+
now?: number;
|
|
230
|
+
path?: string;
|
|
231
|
+
}): Promise<string>;
|
|
232
|
+
declare function muxPlaybackToken(playbackId: string, key: MuxSigningKey, options?: {
|
|
233
|
+
expiresInSeconds?: number;
|
|
234
|
+
now?: number;
|
|
235
|
+
audience?: "v" | "t" | "g" | "s";
|
|
236
|
+
}): Promise<string>;
|
|
237
|
+
declare function muxVideo(options: MuxVideoOptions): VideoProvider & {
|
|
238
|
+
playbackUrl(playbackId: string, opts?: {
|
|
239
|
+
expiresInSeconds?: number;
|
|
240
|
+
}): Promise<string>;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A plain `deliver` for apps without genesis-notifications yet: one Expo push
|
|
245
|
+
* per device token, in requests of 100 (https://docs.expo.dev/push-notifications/sending-notifications/).
|
|
246
|
+
* It returns every ticket, so the app can forget tokens Expo calls
|
|
247
|
+
* `DeviceNotRegistered`. It throws only when nothing at all was sent, so a
|
|
248
|
+
* retry never pushes twice to the devices that already got it.
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
declare const EXPO_SEND_URL = "https://exp.host/--/api/v2/push/send";
|
|
252
|
+
declare const EXPO_BATCH = 100;
|
|
253
|
+
interface ExpoPushMessage {
|
|
254
|
+
to: string;
|
|
255
|
+
title: string;
|
|
256
|
+
body: string;
|
|
257
|
+
data: Record<string, unknown>;
|
|
258
|
+
sound: "default" | null;
|
|
259
|
+
badge: number;
|
|
260
|
+
threadId: string;
|
|
261
|
+
collapseId?: string;
|
|
262
|
+
channelId: string;
|
|
263
|
+
categoryId: string;
|
|
264
|
+
}
|
|
265
|
+
type ExpoPushTicket = {
|
|
266
|
+
status: "ok";
|
|
267
|
+
id: string;
|
|
268
|
+
} | {
|
|
269
|
+
status: "error";
|
|
270
|
+
message?: string;
|
|
271
|
+
details?: {
|
|
272
|
+
error?: string;
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
interface ExpoDeliveryTicket {
|
|
276
|
+
userId: string;
|
|
277
|
+
token: string;
|
|
278
|
+
ticket: ExpoPushTicket;
|
|
279
|
+
}
|
|
280
|
+
interface ExpoDeliverOptions {
|
|
281
|
+
/** The Expo push tokens of these people (any number per person). */
|
|
282
|
+
tokensFor: (userIds: string[]) => Promise<{
|
|
283
|
+
userId: string;
|
|
284
|
+
token: string;
|
|
285
|
+
}[]>;
|
|
286
|
+
fetch?: typeof fetch | undefined;
|
|
287
|
+
/** Required when the Expo project has enhanced push security on. */
|
|
288
|
+
accessToken?: string | undefined;
|
|
289
|
+
/** Default `default`; null for silent. */
|
|
290
|
+
sound?: "default" | null | undefined;
|
|
291
|
+
/** Android channel (default `messages`) and notification category (default `messages`, the category genesis-native's push block registers for chat). */
|
|
292
|
+
channelId?: string | undefined;
|
|
293
|
+
categoryId?: string | undefined;
|
|
294
|
+
/**
|
|
295
|
+
* Replace a conversation's earlier notification with the newest (one per conversation).
|
|
296
|
+
* Off by default: like iMessage and WhatsApp, each message shows, grouped by thread.
|
|
297
|
+
*/
|
|
298
|
+
collapse?: boolean | undefined;
|
|
299
|
+
}
|
|
300
|
+
declare function createExpoDeliver(options: ExpoDeliverOptions): (n: ChatNotification) => Promise<ExpoDeliveryTicket[]>;
|
|
301
|
+
/** The tokens Expo says are gone (`DeviceNotRegistered`): forget them. */
|
|
302
|
+
declare function deadTokens(tickets: readonly ExpoDeliveryTicket[]): string[];
|
|
303
|
+
|
|
304
|
+
interface ChatRunnerHandlerOptions {
|
|
305
|
+
runner: Pick<ChatRunner, "run">;
|
|
306
|
+
/** At least 32 characters. */
|
|
307
|
+
secret: string;
|
|
308
|
+
}
|
|
309
|
+
declare function chatRunnerHandler(options: ChatRunnerHandlerOptions): (request: Request) => Promise<Response>;
|
|
310
|
+
|
|
311
|
+
export { CHAT_MAX_ATTEMPTS, type ChatDeliver, type ChatEffect, type ChatEffectKind, type ChatNotification, type ChatRecipient, type ChatRunReport, type ChatRunner, type ChatRunnerHandlerOptions, type ChatRunnerOptions, type DeepgramOptions, EXPO_BATCH, EXPO_SEND_URL, type ExpoDeliverOptions, type ExpoDeliveryTicket, type ExpoPushMessage, type ExpoPushTicket, type MuxSigningKey, type MuxVideoOptions, type OpenAiTranscriberOptions, type TranscribeInput, type TranscribeResult, type Transcriber, type VideoProvider, chatRunnerHandler, createChatRunner, createExpoDeliver, deadTokens, deepgramTranscriber, muxPlaybackToken, muxPlaybackUrl, muxVideo, openAiTranscriber, verifyMuxSignature };
|