@tipsy-studio/sdk 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @tipsy-studio/sdk
2
2
 
3
- React SDK for Tipsy Studio iframe apps.
3
+ SDK for Tipsy Studio iframe apps.
4
4
 
5
5
  ## Install
6
6
 
@@ -10,38 +10,208 @@ npm install @tipsy-studio/sdk react
10
10
 
11
11
  ## Usage
12
12
 
13
- React-specific exports are available from the `/react` subpath.
13
+ Plain JavaScript and TypeScript clients are available from the package root.
14
+
15
+ ### Non-stream text
16
+
17
+ ```ts
18
+ import { createTipsyStudioClient } from "@tipsy-studio/sdk";
19
+
20
+ const client = createTipsyStudioClient();
21
+
22
+ await client.waitForReady();
23
+
24
+ const response = await client.respond<string>({
25
+ messages: [
26
+ {
27
+ role: "system",
28
+ content: "You are a helpful assistant.",
29
+ },
30
+ {
31
+ role: "user",
32
+ content: "Write a short greeting.",
33
+ },
34
+ ],
35
+ stream: false,
36
+ response_format: { type: "text" },
37
+ });
38
+
39
+ console.log(response.data.result);
40
+ ```
41
+
42
+ ### Stream text
43
+
44
+ ```ts
45
+ import { createTipsyStudioClient } from "@tipsy-studio/sdk";
46
+
47
+ const client = createTipsyStudioClient();
48
+
49
+ let text = "";
50
+
51
+ const result = await client.respond<string>(
52
+ {
53
+ messages: [
54
+ {
55
+ role: "system",
56
+ content: "You are a helpful assistant.",
57
+ },
58
+ {
59
+ role: "user",
60
+ content: "Write a short greeting.",
61
+ },
62
+ ],
63
+ stream: true,
64
+ response_format: { type: "text" },
65
+ },
66
+ {
67
+ onEvent(event) {
68
+ if ("delta" in event) {
69
+ text += event.delta;
70
+ }
71
+ },
72
+ },
73
+ );
74
+
75
+ console.log(text);
76
+ console.log(result.result);
77
+ ```
78
+
79
+ ### Non-stream JSON Schema
80
+
81
+ ```ts
82
+ import { createTipsyStudioClient } from "@tipsy-studio/sdk";
83
+
84
+ type UserProfile = {
85
+ name: string;
86
+ age: number;
87
+ email: string;
88
+ };
89
+
90
+ const client = createTipsyStudioClient();
91
+
92
+ const response = await client.respond<UserProfile>({
93
+ messages: [
94
+ {
95
+ role: "system",
96
+ content: "Return only JSON that matches the schema.",
97
+ },
98
+ {
99
+ role: "user",
100
+ content: "Extract a user from: Alice is 28 and alice@example.com",
101
+ },
102
+ ],
103
+ stream: false,
104
+ response_format: {
105
+ type: "json_schema",
106
+ name: "UserProfile",
107
+ schema: {
108
+ type: "object",
109
+ additionalProperties: false,
110
+ properties: {
111
+ name: { type: "string" },
112
+ age: { type: "integer" },
113
+ email: { type: "string" },
114
+ },
115
+ required: ["name", "age", "email"],
116
+ },
117
+ },
118
+ });
119
+
120
+ console.log(response.data.result.email);
121
+ ```
122
+
123
+ ### Stream JSON Schema
124
+
125
+ ```ts
126
+ import { createTipsyStudioClient } from "@tipsy-studio/sdk";
127
+
128
+ type UserProfile = {
129
+ name: string;
130
+ age: number;
131
+ email: string;
132
+ };
133
+
134
+ const client = createTipsyStudioClient();
135
+ let snapshot: Partial<UserProfile> = {};
136
+
137
+ const result = await client.respond<UserProfile>(
138
+ {
139
+ messages: [
140
+ {
141
+ role: "system",
142
+ content: "Return only JSON that matches the schema.",
143
+ },
144
+ {
145
+ role: "user",
146
+ content: "Extract a user from: Alice is 28 and alice@example.com",
147
+ },
148
+ ],
149
+ stream: true,
150
+ response_format: {
151
+ type: "json_schema",
152
+ name: "UserProfile",
153
+ schema: {
154
+ type: "object",
155
+ additionalProperties: false,
156
+ properties: {
157
+ name: { type: "string" },
158
+ age: { type: "integer" },
159
+ email: { type: "string" },
160
+ },
161
+ required: ["name", "age", "email"],
162
+ },
163
+ },
164
+ },
165
+ {
166
+ onEvent(event) {
167
+ if ("snapshot" in event) {
168
+ snapshot = event.snapshot;
169
+ }
170
+ },
171
+ },
172
+ );
173
+
174
+ console.log(snapshot);
175
+ console.log(result.result);
176
+ ```
177
+
178
+ React-specific exports remain available from the `/react` subpath.
14
179
 
15
180
  ```tsx
16
- import { TipsyStudioProvider, useTipsyChat } from "@tipsy-studio/sdk/react";
181
+ import { TipsyStudioProvider, useTipsyStudio } from "@tipsy-studio/sdk/react";
17
182
 
18
183
  function ChatButton() {
19
- const chat = useTipsyChat();
184
+ const studio = useTipsyStudio();
20
185
 
21
186
  async function handleClick() {
22
- const textParts: string[] = [];
187
+ let text = "";
23
188
 
24
- const result = await chat.completions(
189
+ const result = await studio.respond<string>(
25
190
  {
26
191
  messages: [
27
192
  {
28
193
  role: "system",
29
194
  content: "You are a helpful assistant.",
30
195
  },
196
+ {
197
+ role: "user",
198
+ content: "Write a short greeting.",
199
+ },
31
200
  ],
32
201
  stream: true,
202
+ response_format: { type: "text" },
33
203
  },
34
204
  {
35
- onData(chunk) {
36
- textParts.push(chunk.chunk);
37
- },
38
- onComplete() {
39
- console.log("stream complete", textParts.join(""));
205
+ onEvent(event) {
206
+ if ("delta" in event) {
207
+ text += event.delta;
208
+ }
40
209
  },
41
210
  },
42
211
  );
43
212
 
44
- console.log(result.raw);
213
+ console.log(text);
214
+ console.log(result.result);
45
215
  }
46
216
 
47
217
  return <button onClick={handleClick}>Run chat</button>;
@@ -0,0 +1,318 @@
1
+ // src/bridge.ts
2
+ var TIPSY_BRIDGE_PROTOCOL = "tipsy-bridge-v1";
3
+ var TIPSY_INIT_RETRY_MS = 500;
4
+ var RESPOND_METHOD = "sdk.respond";
5
+ var TipsyBridgeClientError = class extends Error {
6
+ constructor(error) {
7
+ super(error.message);
8
+ this.name = "TipsyBridgeClientError";
9
+ this.code = error.code;
10
+ }
11
+ };
12
+ function getParentOriginFromReferrer() {
13
+ if (typeof document === "undefined" || !document.referrer) {
14
+ return null;
15
+ }
16
+ try {
17
+ return new URL(document.referrer).origin;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ function normalizeTargetOrigin(value) {
23
+ if (value && value.trim()) {
24
+ return value;
25
+ }
26
+ return getParentOriginFromReferrer() ?? "*";
27
+ }
28
+ function isBridgeMessage(value) {
29
+ if (!value || typeof value !== "object") {
30
+ return false;
31
+ }
32
+ const candidate = value;
33
+ if (candidate.protocol !== TIPSY_BRIDGE_PROTOCOL) {
34
+ return false;
35
+ }
36
+ const validTypes = /* @__PURE__ */ new Set([
37
+ "tipsy:init",
38
+ "tipsy:init:ack",
39
+ "tipsy:request",
40
+ "tipsy:stream",
41
+ "tipsy:response",
42
+ "tipsy:error",
43
+ "tipsy:abort"
44
+ ]);
45
+ return typeof candidate.type === "string" && validTypes.has(candidate.type);
46
+ }
47
+ function createRequestId() {
48
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
49
+ return crypto.randomUUID();
50
+ }
51
+ return `tipsy_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
52
+ }
53
+
54
+ // src/client.ts
55
+ var TIPSY_READY_WAIT_RETRY_COUNT = 6;
56
+ function createTipsyStudioClient(options = {}) {
57
+ const sourceWindow = options.sourceWindow ?? (typeof window !== "undefined" ? window : void 0);
58
+ const targetWindow = options.targetWindow ?? (sourceWindow && sourceWindow.parent !== sourceWindow ? sourceWindow.parent : void 0);
59
+ const targetOrigin = normalizeTargetOrigin(options.targetOrigin);
60
+ const pendingRequests = /* @__PURE__ */ new Map();
61
+ const readyWaiters = /* @__PURE__ */ new Set();
62
+ const listeners = /* @__PURE__ */ new Set();
63
+ let isDestroyed = false;
64
+ let isConnected = false;
65
+ let isReady = false;
66
+ let initIntervalId = null;
67
+ const emitReadyChange = (nextReady) => {
68
+ if (isReady === nextReady) {
69
+ return;
70
+ }
71
+ isReady = nextReady;
72
+ for (const listener of listeners) {
73
+ listener(isReady);
74
+ }
75
+ };
76
+ const resolveReadyWaiters = () => {
77
+ const waiters = Array.from(readyWaiters.values());
78
+ readyWaiters.clear();
79
+ for (const resolve of waiters) {
80
+ resolve();
81
+ }
82
+ };
83
+ const rejectPending = (error) => {
84
+ const requests = Array.from(pendingRequests.values());
85
+ pendingRequests.clear();
86
+ for (const pending of requests) {
87
+ pending.cleanupAbortListener?.();
88
+ pending.reject(new TipsyBridgeClientError(error));
89
+ }
90
+ };
91
+ const postMessageToTarget = (message) => {
92
+ targetWindow?.postMessage(message, targetOrigin);
93
+ };
94
+ const postInit = () => {
95
+ if (isDestroyed || isReady || !targetWindow) {
96
+ return;
97
+ }
98
+ postMessageToTarget({
99
+ protocol: TIPSY_BRIDGE_PROTOCOL,
100
+ type: "tipsy:init"
101
+ });
102
+ };
103
+ const handleMessage = (event) => {
104
+ if (isDestroyed || event.source !== targetWindow) {
105
+ return;
106
+ }
107
+ if (targetOrigin !== "*" && event.origin !== targetOrigin) {
108
+ return;
109
+ }
110
+ if (!isBridgeMessage(event.data)) {
111
+ return;
112
+ }
113
+ if (event.data.type === "tipsy:init:ack") {
114
+ emitReadyChange(true);
115
+ resolveReadyWaiters();
116
+ return;
117
+ }
118
+ if (event.data.type === "tipsy:error") {
119
+ if (!event.data.requestId) {
120
+ return;
121
+ }
122
+ const pending = pendingRequests.get(event.data.requestId);
123
+ if (!pending) {
124
+ return;
125
+ }
126
+ pendingRequests.delete(event.data.requestId);
127
+ pending.cleanupAbortListener?.();
128
+ pending.reject(new TipsyBridgeClientError(event.data.error));
129
+ return;
130
+ }
131
+ if (event.data.type === "tipsy:stream") {
132
+ const pending = pendingRequests.get(event.data.requestId);
133
+ if (!pending || pending.method !== event.data.payload.method) {
134
+ return;
135
+ }
136
+ pending.onEvent?.(event.data.payload.event);
137
+ return;
138
+ }
139
+ if (event.data.type === "tipsy:response") {
140
+ const pending = pendingRequests.get(event.data.requestId);
141
+ if (!pending || pending.method !== event.data.payload.method) {
142
+ return;
143
+ }
144
+ pendingRequests.delete(event.data.requestId);
145
+ pending.cleanupAbortListener?.();
146
+ pending.resolve(event.data.payload.result);
147
+ }
148
+ };
149
+ const ensureConnected = () => {
150
+ if (isDestroyed || isConnected || !sourceWindow) {
151
+ return;
152
+ }
153
+ sourceWindow.addEventListener("message", handleMessage);
154
+ initIntervalId = sourceWindow.setInterval(postInit, TIPSY_INIT_RETRY_MS);
155
+ isConnected = true;
156
+ postInit();
157
+ };
158
+ const ensureBridgeAvailable = () => {
159
+ if (!sourceWindow || !targetWindow) {
160
+ throw new TipsyBridgeClientError({
161
+ code: "NETWORK_ERROR",
162
+ message: "Tipsy bridge is only available inside an iframe."
163
+ });
164
+ }
165
+ };
166
+ const waitForReady = async (signal) => {
167
+ if (isDestroyed) {
168
+ throw new TipsyBridgeClientError({
169
+ code: "NETWORK_ERROR",
170
+ message: "Tipsy bridge was disconnected."
171
+ });
172
+ }
173
+ ensureBridgeAvailable();
174
+ ensureConnected();
175
+ const activeSourceWindow = sourceWindow;
176
+ if (!activeSourceWindow) {
177
+ throw new TipsyBridgeClientError({
178
+ code: "NETWORK_ERROR",
179
+ message: "Tipsy bridge is only available inside an iframe."
180
+ });
181
+ }
182
+ if (isReady) {
183
+ return;
184
+ }
185
+ for (let attempt = 0; attempt < TIPSY_READY_WAIT_RETRY_COUNT; attempt += 1) {
186
+ if (signal?.aborted) {
187
+ throw new DOMException("The operation was aborted.", "AbortError");
188
+ }
189
+ postInit();
190
+ await new Promise((resolve, reject) => {
191
+ let timeoutId = 0;
192
+ const handleReady = () => {
193
+ activeSourceWindow.clearTimeout(timeoutId);
194
+ signal?.removeEventListener("abort", handleAbort);
195
+ readyWaiters.delete(handleReady);
196
+ resolve();
197
+ };
198
+ const handleAbort = () => {
199
+ activeSourceWindow.clearTimeout(timeoutId);
200
+ readyWaiters.delete(handleReady);
201
+ signal?.removeEventListener("abort", handleAbort);
202
+ reject(new DOMException("The operation was aborted.", "AbortError"));
203
+ };
204
+ readyWaiters.add(handleReady);
205
+ timeoutId = activeSourceWindow.setTimeout(() => {
206
+ readyWaiters.delete(handleReady);
207
+ signal?.removeEventListener("abort", handleAbort);
208
+ resolve();
209
+ }, TIPSY_INIT_RETRY_MS);
210
+ signal?.addEventListener("abort", handleAbort, { once: true });
211
+ });
212
+ if (isReady) {
213
+ return;
214
+ }
215
+ }
216
+ throw new TipsyBridgeClientError({
217
+ code: "NETWORK_ERROR",
218
+ message: "Tipsy bridge is not ready."
219
+ });
220
+ };
221
+ function respond(request, controller) {
222
+ return client.call(RESPOND_METHOD, request, {
223
+ signal: controller?.signal,
224
+ onEvent: controller?.onEvent
225
+ });
226
+ }
227
+ const client = {
228
+ isReady: () => isReady,
229
+ subscribe(listener) {
230
+ listeners.add(listener);
231
+ listener(isReady);
232
+ ensureConnected();
233
+ return () => {
234
+ listeners.delete(listener);
235
+ };
236
+ },
237
+ async waitForReady(signal) {
238
+ await waitForReady(signal);
239
+ },
240
+ async call(method, params, controller) {
241
+ if (isDestroyed) {
242
+ throw new TipsyBridgeClientError({
243
+ code: "NETWORK_ERROR",
244
+ message: "Tipsy bridge was disconnected."
245
+ });
246
+ }
247
+ await waitForReady(controller?.signal);
248
+ const requestId = createRequestId();
249
+ return await new Promise(
250
+ (resolve, reject) => {
251
+ const pending = {
252
+ method,
253
+ resolve: (value) => resolve(value),
254
+ reject,
255
+ onEvent: controller?.onEvent
256
+ };
257
+ pendingRequests.set(requestId, pending);
258
+ const abortSignal = controller?.signal;
259
+ const handleAbort = () => {
260
+ if (!pendingRequests.has(requestId)) {
261
+ return;
262
+ }
263
+ pendingRequests.delete(requestId);
264
+ pending.cleanupAbortListener?.();
265
+ postMessageToTarget({
266
+ protocol: TIPSY_BRIDGE_PROTOCOL,
267
+ type: "tipsy:abort",
268
+ requestId
269
+ });
270
+ reject(new DOMException("The operation was aborted.", "AbortError"));
271
+ };
272
+ pending.cleanupAbortListener = () => {
273
+ abortSignal?.removeEventListener("abort", handleAbort);
274
+ };
275
+ if (abortSignal?.aborted) {
276
+ handleAbort();
277
+ return;
278
+ }
279
+ abortSignal?.addEventListener("abort", handleAbort, { once: true });
280
+ postMessageToTarget({
281
+ protocol: TIPSY_BRIDGE_PROTOCOL,
282
+ type: "tipsy:request",
283
+ requestId,
284
+ method,
285
+ params
286
+ });
287
+ }
288
+ );
289
+ },
290
+ respond,
291
+ destroy() {
292
+ if (isDestroyed) {
293
+ return;
294
+ }
295
+ isDestroyed = true;
296
+ resolveReadyWaiters();
297
+ emitReadyChange(false);
298
+ listeners.clear();
299
+ if (isConnected && sourceWindow) {
300
+ if (initIntervalId !== null) {
301
+ sourceWindow.clearInterval(initIntervalId);
302
+ }
303
+ sourceWindow.removeEventListener("message", handleMessage);
304
+ }
305
+ initIntervalId = null;
306
+ isConnected = false;
307
+ rejectPending({
308
+ code: "NETWORK_ERROR",
309
+ message: "Tipsy bridge was disconnected."
310
+ });
311
+ }
312
+ };
313
+ return client;
314
+ }
315
+
316
+ export { RESPOND_METHOD, TIPSY_BRIDGE_PROTOCOL, TIPSY_INIT_RETRY_MS, TipsyBridgeClientError, createRequestId, createTipsyStudioClient, getParentOriginFromReferrer, isBridgeMessage, normalizeTargetOrigin };
317
+ //# sourceMappingURL=chunk-NLCN4YRK.js.map
318
+ //# sourceMappingURL=chunk-NLCN4YRK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridge.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-NLCN4YRK.js","sourcesContent":["export const TIPSY_BRIDGE_PROTOCOL = \"tipsy-bridge-v1\";\nexport const TIPSY_INIT_RETRY_MS = 500;\nexport const RESPOND_METHOD = \"sdk.respond\";\n\ntype TipsyBridgeMessageType =\n | \"tipsy:init\"\n | \"tipsy:init:ack\"\n | \"tipsy:request\"\n | \"tipsy:stream\"\n | \"tipsy:response\"\n | \"tipsy:error\"\n | \"tipsy:abort\";\n\nexport type TipsyRole = \"system\" | \"user\" | \"assistant\";\n\nexport type TipsyMessage = {\n role: TipsyRole;\n content: string;\n};\n\nexport type TipsyUsage = {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n};\n\nexport type TipsyTextResponseFormat = {\n type: \"text\";\n};\n\nexport type TipsyJsonSchemaResponseFormat = {\n type: \"json_schema\";\n name: string;\n schema: Record<string, unknown>;\n};\n\nexport type TipsyRespondResponseFormat =\n | TipsyTextResponseFormat\n | TipsyJsonSchemaResponseFormat;\n\ntype TipsyRespondRequestBase = {\n messages: TipsyMessage[];\n model_id?: string | null;\n response_format: TipsyRespondResponseFormat;\n};\n\nexport type TipsyRespondStreamRequest = TipsyRespondRequestBase & {\n stream: true;\n};\n\nexport type TipsyRespondNonStreamRequest = TipsyRespondRequestBase & {\n stream: false;\n};\n\nexport type TipsyRespondRequest =\n | TipsyRespondStreamRequest\n | TipsyRespondNonStreamRequest;\n\nexport type TipsyRespondEnvelope<T = unknown> = {\n code: number;\n msg: string;\n trace_id: string;\n data: {\n message_id: string;\n result: T;\n response_format:\n | TipsyTextResponseFormat\n | {\n type: \"json_schema\";\n name: string;\n };\n usage: TipsyUsage;\n };\n};\n\nexport type TipsyRespondTextStreamEvent = {\n type: \"delta\";\n delta: string;\n};\n\nexport type TipsyRespondJsonStreamEvent<T = unknown> = {\n type: \"delta\";\n snapshot: Partial<T> | T;\n};\n\nexport type TipsyRespondMetaEvent = {\n type: \"meta\";\n message_id: string;\n usage: TipsyUsage;\n trace_id: string;\n};\n\nexport type TipsyRespondStreamEvent<T = unknown> =\n | TipsyRespondTextStreamEvent\n | TipsyRespondJsonStreamEvent<T>\n | TipsyRespondMetaEvent;\n\nexport type TipsyRespondResult<T = unknown> = {\n message_id: string;\n result: T;\n response_format:\n | TipsyTextResponseFormat\n | {\n type: \"json_schema\";\n name: string;\n };\n};\n\nexport type TipsyBridgeErrorCode =\n | \"UNAUTHORIZED\"\n | \"BAD_REQUEST\"\n | \"NETWORK_ERROR\"\n | \"STREAM_PARSE_ERROR\"\n | \"ORIGIN_MISMATCH\"\n | \"UNSUPPORTED_METHOD\"\n | \"INTERNAL_ERROR\";\n\nexport type TipsyBridgeError = {\n code: TipsyBridgeErrorCode;\n message: string;\n};\n\nexport type TipsyBridgeRequestMap = {\n [RESPOND_METHOD]: TipsyRespondRequest;\n};\n\nexport type TipsyBridgeStreamMap = {\n [RESPOND_METHOD]: TipsyRespondStreamEvent;\n};\n\nexport type TipsyBridgeResponseMap = {\n [RESPOND_METHOD]: TipsyRespondEnvelope | TipsyRespondResult;\n};\n\nexport type TipsyInitMessage = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:init\";\n};\n\ntype TipsyInitAckMessage = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:init:ack\";\n};\n\nexport type TipsyRequestMessage<M extends keyof TipsyBridgeRequestMap> = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:request\";\n requestId: string;\n method: M;\n params: TipsyBridgeRequestMap[M];\n};\n\ntype TipsyStreamMessage<M extends keyof TipsyBridgeStreamMap> = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:stream\";\n requestId: string;\n payload: {\n method: M;\n event: TipsyBridgeStreamMap[M];\n };\n};\n\ntype TipsyResponseMessage<M extends keyof TipsyBridgeResponseMap> = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:response\";\n requestId: string;\n payload: {\n method: M;\n result: TipsyBridgeResponseMap[M];\n };\n};\n\ntype TipsyErrorMessage = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:error\";\n requestId?: string;\n error: TipsyBridgeError;\n};\n\nexport type TipsyAbortMessage = {\n protocol: typeof TIPSY_BRIDGE_PROTOCOL;\n type: \"tipsy:abort\";\n requestId: string;\n};\n\nexport type TipsyBridgeMessage =\n | TipsyInitMessage\n | TipsyInitAckMessage\n | TipsyRequestMessage<keyof TipsyBridgeRequestMap>\n | TipsyStreamMessage<keyof TipsyBridgeStreamMap>\n | TipsyResponseMessage<keyof TipsyBridgeResponseMap>\n | TipsyErrorMessage\n | TipsyAbortMessage;\n\nexport type TipsyRequestController<M extends keyof TipsyBridgeRequestMap> = {\n signal?: AbortSignal;\n onEvent?: (event: TipsyBridgeStreamMap[M]) => void;\n};\n\nexport type TipsyRespondController<T = unknown> = {\n signal?: AbortSignal;\n onEvent?: (event: TipsyRespondStreamEvent<T>) => void;\n};\n\nexport class TipsyBridgeClientError extends Error {\n code: TipsyBridgeErrorCode;\n\n constructor(error: TipsyBridgeError) {\n super(error.message);\n this.name = \"TipsyBridgeClientError\";\n this.code = error.code;\n }\n}\n\nexport function getParentOriginFromReferrer(): string | null {\n if (typeof document === \"undefined\" || !document.referrer) {\n return null;\n }\n\n try {\n return new URL(document.referrer).origin;\n } catch {\n return null;\n }\n}\n\nexport function normalizeTargetOrigin(value?: string): string {\n if (value && value.trim()) {\n return value;\n }\n\n return getParentOriginFromReferrer() ?? \"*\";\n}\n\nexport function isBridgeMessage(value: unknown): value is TipsyBridgeMessage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Partial<TipsyBridgeMessage>;\n if (candidate.protocol !== TIPSY_BRIDGE_PROTOCOL) {\n return false;\n }\n\n const validTypes = new Set<TipsyBridgeMessageType>([\n \"tipsy:init\",\n \"tipsy:init:ack\",\n \"tipsy:request\",\n \"tipsy:stream\",\n \"tipsy:response\",\n \"tipsy:error\",\n \"tipsy:abort\",\n ]);\n\n return typeof candidate.type === \"string\" && validTypes.has(candidate.type);\n}\n\nexport function createRequestId() {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n\n return `tipsy_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n}\n","import {\n RESPOND_METHOD,\n TIPSY_BRIDGE_PROTOCOL,\n TIPSY_INIT_RETRY_MS,\n type TipsyAbortMessage,\n type TipsyBridgeError,\n TipsyBridgeClientError,\n type TipsyBridgeRequestMap,\n type TipsyBridgeResponseMap,\n type TipsyBridgeStreamMap,\n type TipsyInitMessage,\n type TipsyRequestController,\n type TipsyRequestMessage,\n type TipsyRespondController,\n type TipsyRespondEnvelope,\n type TipsyRespondNonStreamRequest,\n type TipsyRespondRequest,\n type TipsyRespondResult,\n type TipsyRespondStreamRequest,\n isBridgeMessage,\n normalizeTargetOrigin,\n createRequestId,\n} from \"./bridge\";\n\nconst TIPSY_READY_WAIT_RETRY_COUNT = 6;\n\ntype PendingRequest = {\n method: keyof TipsyBridgeRequestMap;\n resolve: (value: TipsyBridgeResponseMap[keyof TipsyBridgeResponseMap]) => void;\n reject: (reason?: unknown) => void;\n onEvent?: (event: any) => void;\n cleanupAbortListener?: () => void;\n};\n\nexport type TipsyStudioClientOptions = {\n targetOrigin?: string;\n sourceWindow?: Window;\n targetWindow?: Window;\n};\n\nexport type TipsyStudioClient = {\n isReady: () => boolean;\n subscribe: (listener: (isReady: boolean) => void) => () => void;\n waitForReady: (signal?: AbortSignal) => Promise<void>;\n call: <M extends keyof TipsyBridgeRequestMap>(\n method: M,\n params: TipsyBridgeRequestMap[M],\n controller?: TipsyRequestController<M>,\n ) => Promise<TipsyBridgeResponseMap[M]>;\n respond<T = string>(\n request: TipsyRespondNonStreamRequest,\n controller?: TipsyRespondController<T>,\n ): Promise<TipsyRespondEnvelope<T>>;\n respond<T = string>(\n request: TipsyRespondStreamRequest,\n controller?: TipsyRespondController<T>,\n ): Promise<TipsyRespondResult<T>>;\n respond<T = string>(\n request: TipsyRespondRequest,\n controller?: TipsyRespondController<T>,\n ): Promise<TipsyRespondEnvelope<T> | TipsyRespondResult<T>>;\n destroy: () => void;\n};\n\nexport function createTipsyStudioClient(\n options: TipsyStudioClientOptions = {},\n): TipsyStudioClient {\n const sourceWindow =\n options.sourceWindow ?? (typeof window !== \"undefined\" ? window : undefined);\n const targetWindow =\n options.targetWindow ??\n (sourceWindow && sourceWindow.parent !== sourceWindow\n ? sourceWindow.parent\n : undefined);\n const targetOrigin = normalizeTargetOrigin(options.targetOrigin);\n\n const pendingRequests = new Map<string, PendingRequest>();\n const readyWaiters = new Set<() => void>();\n const listeners = new Set<(isReady: boolean) => void>();\n\n let isDestroyed = false;\n let isConnected = false;\n let isReady = false;\n let initIntervalId: number | null = null;\n\n const emitReadyChange = (nextReady: boolean) => {\n if (isReady === nextReady) {\n return;\n }\n\n isReady = nextReady;\n for (const listener of listeners) {\n listener(isReady);\n }\n };\n\n const resolveReadyWaiters = () => {\n const waiters = Array.from(readyWaiters.values());\n readyWaiters.clear();\n for (const resolve of waiters) {\n resolve();\n }\n };\n\n const rejectPending = (error: TipsyBridgeError) => {\n const requests = Array.from(pendingRequests.values());\n pendingRequests.clear();\n for (const pending of requests) {\n pending.cleanupAbortListener?.();\n pending.reject(new TipsyBridgeClientError(error));\n }\n };\n\n const postMessageToTarget = (\n message:\n | TipsyInitMessage\n | TipsyAbortMessage\n | TipsyRequestMessage<keyof TipsyBridgeRequestMap>,\n ) => {\n targetWindow?.postMessage(message, targetOrigin);\n };\n\n const postInit = () => {\n if (isDestroyed || isReady || !targetWindow) {\n return;\n }\n\n postMessageToTarget({\n protocol: TIPSY_BRIDGE_PROTOCOL,\n type: \"tipsy:init\",\n });\n };\n\n const handleMessage = (event: MessageEvent) => {\n if (isDestroyed || event.source !== targetWindow) {\n return;\n }\n\n if (targetOrigin !== \"*\" && event.origin !== targetOrigin) {\n return;\n }\n\n if (!isBridgeMessage(event.data)) {\n return;\n }\n\n if (event.data.type === \"tipsy:init:ack\") {\n emitReadyChange(true);\n resolveReadyWaiters();\n return;\n }\n\n if (event.data.type === \"tipsy:error\") {\n if (!event.data.requestId) {\n return;\n }\n\n const pending = pendingRequests.get(event.data.requestId);\n if (!pending) {\n return;\n }\n\n pendingRequests.delete(event.data.requestId);\n pending.cleanupAbortListener?.();\n pending.reject(new TipsyBridgeClientError(event.data.error));\n return;\n }\n\n if (event.data.type === \"tipsy:stream\") {\n const pending = pendingRequests.get(event.data.requestId);\n if (!pending || pending.method !== event.data.payload.method) {\n return;\n }\n\n pending.onEvent?.(event.data.payload.event);\n return;\n }\n\n if (event.data.type === \"tipsy:response\") {\n const pending = pendingRequests.get(event.data.requestId);\n if (!pending || pending.method !== event.data.payload.method) {\n return;\n }\n\n pendingRequests.delete(event.data.requestId);\n pending.cleanupAbortListener?.();\n pending.resolve(event.data.payload.result);\n }\n };\n\n const ensureConnected = () => {\n if (isDestroyed || isConnected || !sourceWindow) {\n return;\n }\n\n sourceWindow.addEventListener(\"message\", handleMessage);\n initIntervalId = sourceWindow.setInterval(postInit, TIPSY_INIT_RETRY_MS);\n isConnected = true;\n postInit();\n };\n\n const ensureBridgeAvailable = () => {\n if (!sourceWindow || !targetWindow) {\n throw new TipsyBridgeClientError({\n code: \"NETWORK_ERROR\",\n message: \"Tipsy bridge is only available inside an iframe.\",\n });\n }\n };\n\n const waitForReady = async (signal?: AbortSignal) => {\n if (isDestroyed) {\n throw new TipsyBridgeClientError({\n code: \"NETWORK_ERROR\",\n message: \"Tipsy bridge was disconnected.\",\n });\n }\n\n ensureBridgeAvailable();\n ensureConnected();\n const activeSourceWindow = sourceWindow;\n\n if (!activeSourceWindow) {\n throw new TipsyBridgeClientError({\n code: \"NETWORK_ERROR\",\n message: \"Tipsy bridge is only available inside an iframe.\",\n });\n }\n\n if (isReady) {\n return;\n }\n\n for (let attempt = 0; attempt < TIPSY_READY_WAIT_RETRY_COUNT; attempt += 1) {\n if (signal?.aborted) {\n throw new DOMException(\"The operation was aborted.\", \"AbortError\");\n }\n\n postInit();\n\n await new Promise<void>((resolve, reject) => {\n let timeoutId = 0;\n\n const handleReady = () => {\n activeSourceWindow.clearTimeout(timeoutId);\n signal?.removeEventListener(\"abort\", handleAbort);\n readyWaiters.delete(handleReady);\n resolve();\n };\n\n const handleAbort = () => {\n activeSourceWindow.clearTimeout(timeoutId);\n readyWaiters.delete(handleReady);\n signal?.removeEventListener(\"abort\", handleAbort);\n reject(new DOMException(\"The operation was aborted.\", \"AbortError\"));\n };\n\n readyWaiters.add(handleReady);\n timeoutId = activeSourceWindow.setTimeout(() => {\n readyWaiters.delete(handleReady);\n signal?.removeEventListener(\"abort\", handleAbort);\n resolve();\n }, TIPSY_INIT_RETRY_MS);\n\n signal?.addEventListener(\"abort\", handleAbort, { once: true });\n });\n\n if (isReady) {\n return;\n }\n }\n\n throw new TipsyBridgeClientError({\n code: \"NETWORK_ERROR\",\n message: \"Tipsy bridge is not ready.\",\n });\n };\n\n function respond<T = string>(\n request: TipsyRespondNonStreamRequest,\n controller?: TipsyRespondController<T>,\n ): Promise<TipsyRespondEnvelope<T>>;\n function respond<T = string>(\n request: TipsyRespondStreamRequest,\n controller?: TipsyRespondController<T>,\n ): Promise<TipsyRespondResult<T>>;\n function respond<T = string>(\n request: TipsyRespondRequest,\n controller?: TipsyRespondController<T>,\n ): Promise<TipsyRespondEnvelope<T> | TipsyRespondResult<T>> {\n return client.call(RESPOND_METHOD, request, {\n signal: controller?.signal,\n onEvent: controller?.onEvent as\n | ((event: TipsyBridgeStreamMap[typeof RESPOND_METHOD]) => void)\n | undefined,\n }) as Promise<TipsyRespondEnvelope<T> | TipsyRespondResult<T>>;\n }\n\n const client: TipsyStudioClient = {\n isReady: () => isReady,\n subscribe(listener) {\n listeners.add(listener);\n listener(isReady);\n ensureConnected();\n\n return () => {\n listeners.delete(listener);\n };\n },\n async waitForReady(signal) {\n await waitForReady(signal);\n },\n async call(method, params, controller) {\n if (isDestroyed) {\n throw new TipsyBridgeClientError({\n code: \"NETWORK_ERROR\",\n message: \"Tipsy bridge was disconnected.\",\n });\n }\n\n await waitForReady(controller?.signal);\n\n const requestId = createRequestId();\n\n return await new Promise<TipsyBridgeResponseMap[typeof method]>(\n (resolve, reject) => {\n const pending: PendingRequest = {\n method,\n resolve: (value) =>\n resolve(value as TipsyBridgeResponseMap[typeof method]),\n reject,\n onEvent: controller?.onEvent as\n | ((event: TipsyBridgeStreamMap[typeof method]) => void)\n | undefined,\n };\n\n pendingRequests.set(requestId, pending);\n\n const abortSignal = controller?.signal;\n const handleAbort = () => {\n if (!pendingRequests.has(requestId)) {\n return;\n }\n\n pendingRequests.delete(requestId);\n pending.cleanupAbortListener?.();\n postMessageToTarget({\n protocol: TIPSY_BRIDGE_PROTOCOL,\n type: \"tipsy:abort\",\n requestId,\n });\n reject(new DOMException(\"The operation was aborted.\", \"AbortError\"));\n };\n\n pending.cleanupAbortListener = () => {\n abortSignal?.removeEventListener(\"abort\", handleAbort);\n };\n\n if (abortSignal?.aborted) {\n handleAbort();\n return;\n }\n\n abortSignal?.addEventListener(\"abort\", handleAbort, { once: true });\n\n postMessageToTarget({\n protocol: TIPSY_BRIDGE_PROTOCOL,\n type: \"tipsy:request\",\n requestId,\n method,\n params,\n });\n },\n );\n },\n respond,\n destroy() {\n if (isDestroyed) {\n return;\n }\n\n isDestroyed = true;\n resolveReadyWaiters();\n emitReadyChange(false);\n listeners.clear();\n\n if (isConnected && sourceWindow) {\n if (initIntervalId !== null) {\n sourceWindow.clearInterval(initIntervalId);\n }\n sourceWindow.removeEventListener(\"message\", handleMessage);\n }\n\n initIntervalId = null;\n isConnected = false;\n rejectPending({\n code: \"NETWORK_ERROR\",\n message: \"Tipsy bridge was disconnected.\",\n });\n },\n };\n\n return client;\n}\n"]}