@scalemule/nextjs 0.0.32 → 0.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +29 -39
- package/dist/index.d.ts +29 -39
- package/dist/index.js +82 -171
- package/dist/index.mjs +82 -171
- package/dist/server/index.d.mts +44 -10
- package/dist/server/index.d.ts +44 -10
- package/dist/server/index.js +68 -0
- package/dist/server/index.mjs +68 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { RealtimeService } from '@scalemule/sdk';
|
|
3
4
|
import { ScaleMuleClient } from './client.mjs';
|
|
4
5
|
export { ClientConfig, RequestOptions, createClient } from './client.mjs';
|
|
5
6
|
import { S as ScaleMuleConfig, U as User, L as LoginResponse, A as ApiError, a as UseAuthReturn, b as UseBillingReturn, c as ListFilesParams, d as UseContentReturn, e as UseUserReturn, f as UseAnalyticsOptions, g as UseAnalyticsReturn } from './index-BoENfro3.mjs';
|
|
@@ -8,6 +9,8 @@ export { h as AccountBalance, i as AnalyticsEvent, j as ApiResponse, B as BatchT
|
|
|
8
9
|
interface ScaleMuleContextValue {
|
|
9
10
|
/** The API client instance */
|
|
10
11
|
client: ScaleMuleClient;
|
|
12
|
+
/** Base SDK realtime service — shared singleton for WebSocket connections */
|
|
13
|
+
realtime: RealtimeService;
|
|
11
14
|
/** Current authenticated user */
|
|
12
15
|
user: User | null;
|
|
13
16
|
/** Set the current user */
|
|
@@ -154,58 +157,45 @@ interface RealtimeMessage<T = unknown> {
|
|
|
154
157
|
}
|
|
155
158
|
type RealtimeStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
|
156
159
|
interface UseRealtimeOptions {
|
|
157
|
-
/**
|
|
160
|
+
/** Channels to subscribe to */
|
|
161
|
+
channels?: string[];
|
|
162
|
+
/** Called when a message arrives on any subscribed channel */
|
|
163
|
+
onMessage?: (channel: string, data: unknown) => void;
|
|
164
|
+
/** Auto-connect on mount (default: true) — subscribing auto-connects */
|
|
158
165
|
autoConnect?: boolean;
|
|
159
|
-
/** Events to subscribe to (default: all) */
|
|
160
|
-
events?: RealtimeEvent[];
|
|
161
|
-
/** Reconnect on disconnect (default: true) */
|
|
162
|
-
autoReconnect?: boolean;
|
|
163
|
-
/** Max reconnect attempts (default: 5) */
|
|
164
|
-
maxReconnectAttempts?: number;
|
|
165
|
-
/** Reconnect delay in ms (default: 1000, doubles each attempt) */
|
|
166
|
-
reconnectDelay?: number;
|
|
167
166
|
}
|
|
168
167
|
interface UseRealtimeReturn {
|
|
169
168
|
/** Current connection status */
|
|
170
169
|
status: RealtimeStatus;
|
|
171
|
-
/** Last error */
|
|
172
|
-
error: ApiError | null;
|
|
173
|
-
/** Connect to realtime */
|
|
174
|
-
connect: () => void;
|
|
175
|
-
/** Disconnect from realtime */
|
|
176
|
-
disconnect: () => void;
|
|
177
|
-
/** Subscribe to an event */
|
|
178
|
-
subscribe: <T>(event: RealtimeEvent, callback: (data: T) => void) => () => void;
|
|
179
|
-
/** Send a message (if supported) */
|
|
180
|
-
send: (event: string, data: unknown) => void;
|
|
181
170
|
/** Last received message */
|
|
182
|
-
lastMessage:
|
|
171
|
+
lastMessage: {
|
|
172
|
+
channel: string;
|
|
173
|
+
data: unknown;
|
|
174
|
+
} | null;
|
|
175
|
+
/** Manually disconnect */
|
|
176
|
+
disconnect: () => void;
|
|
177
|
+
/** Subscribe to an additional channel (auto-connects) */
|
|
178
|
+
subscribe: (channel: string, callback?: (data: unknown) => void) => () => void;
|
|
179
|
+
/** Publish data to a channel */
|
|
180
|
+
publish: (channel: string, data: unknown) => void;
|
|
183
181
|
}
|
|
184
182
|
/**
|
|
185
|
-
* Real-time updates hook via WebSocket
|
|
183
|
+
* Real-time updates hook via WebSocket.
|
|
186
184
|
*
|
|
187
|
-
*
|
|
185
|
+
* Uses the base SDK's RealtimeService (shared singleton created in the provider)
|
|
186
|
+
* to ensure correct protocol handling and single WebSocket connection per page.
|
|
188
187
|
*
|
|
189
188
|
* @example
|
|
190
189
|
* ```tsx
|
|
191
|
-
* function
|
|
192
|
-
* const { status,
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* refreshFiles()
|
|
199
|
-
* })
|
|
200
|
-
*
|
|
201
|
-
* return () => unsubscribe()
|
|
202
|
-
* }, [subscribe])
|
|
190
|
+
* function ChatNotifications() {
|
|
191
|
+
* const { status, lastMessage } = useRealtime({
|
|
192
|
+
* channels: ['chat:room-1', 'notifications'],
|
|
193
|
+
* onMessage: (channel, data) => {
|
|
194
|
+
* console.log(`${channel}:`, data)
|
|
195
|
+
* },
|
|
196
|
+
* })
|
|
203
197
|
*
|
|
204
|
-
* return
|
|
205
|
-
* <div>
|
|
206
|
-
* <span>Status: {status}</span>
|
|
207
|
-
* </div>
|
|
208
|
-
* )
|
|
198
|
+
* return <div>Status: {status}</div>
|
|
209
199
|
* }
|
|
210
200
|
* ```
|
|
211
201
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { RealtimeService } from '@scalemule/sdk';
|
|
3
4
|
import { ScaleMuleClient } from './client.js';
|
|
4
5
|
export { ClientConfig, RequestOptions, createClient } from './client.js';
|
|
5
6
|
import { S as ScaleMuleConfig, U as User, L as LoginResponse, A as ApiError, a as UseAuthReturn, b as UseBillingReturn, c as ListFilesParams, d as UseContentReturn, e as UseUserReturn, f as UseAnalyticsOptions, g as UseAnalyticsReturn } from './index-BoENfro3.js';
|
|
@@ -8,6 +9,8 @@ export { h as AccountBalance, i as AnalyticsEvent, j as ApiResponse, B as BatchT
|
|
|
8
9
|
interface ScaleMuleContextValue {
|
|
9
10
|
/** The API client instance */
|
|
10
11
|
client: ScaleMuleClient;
|
|
12
|
+
/** Base SDK realtime service — shared singleton for WebSocket connections */
|
|
13
|
+
realtime: RealtimeService;
|
|
11
14
|
/** Current authenticated user */
|
|
12
15
|
user: User | null;
|
|
13
16
|
/** Set the current user */
|
|
@@ -154,58 +157,45 @@ interface RealtimeMessage<T = unknown> {
|
|
|
154
157
|
}
|
|
155
158
|
type RealtimeStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
|
156
159
|
interface UseRealtimeOptions {
|
|
157
|
-
/**
|
|
160
|
+
/** Channels to subscribe to */
|
|
161
|
+
channels?: string[];
|
|
162
|
+
/** Called when a message arrives on any subscribed channel */
|
|
163
|
+
onMessage?: (channel: string, data: unknown) => void;
|
|
164
|
+
/** Auto-connect on mount (default: true) — subscribing auto-connects */
|
|
158
165
|
autoConnect?: boolean;
|
|
159
|
-
/** Events to subscribe to (default: all) */
|
|
160
|
-
events?: RealtimeEvent[];
|
|
161
|
-
/** Reconnect on disconnect (default: true) */
|
|
162
|
-
autoReconnect?: boolean;
|
|
163
|
-
/** Max reconnect attempts (default: 5) */
|
|
164
|
-
maxReconnectAttempts?: number;
|
|
165
|
-
/** Reconnect delay in ms (default: 1000, doubles each attempt) */
|
|
166
|
-
reconnectDelay?: number;
|
|
167
166
|
}
|
|
168
167
|
interface UseRealtimeReturn {
|
|
169
168
|
/** Current connection status */
|
|
170
169
|
status: RealtimeStatus;
|
|
171
|
-
/** Last error */
|
|
172
|
-
error: ApiError | null;
|
|
173
|
-
/** Connect to realtime */
|
|
174
|
-
connect: () => void;
|
|
175
|
-
/** Disconnect from realtime */
|
|
176
|
-
disconnect: () => void;
|
|
177
|
-
/** Subscribe to an event */
|
|
178
|
-
subscribe: <T>(event: RealtimeEvent, callback: (data: T) => void) => () => void;
|
|
179
|
-
/** Send a message (if supported) */
|
|
180
|
-
send: (event: string, data: unknown) => void;
|
|
181
170
|
/** Last received message */
|
|
182
|
-
lastMessage:
|
|
171
|
+
lastMessage: {
|
|
172
|
+
channel: string;
|
|
173
|
+
data: unknown;
|
|
174
|
+
} | null;
|
|
175
|
+
/** Manually disconnect */
|
|
176
|
+
disconnect: () => void;
|
|
177
|
+
/** Subscribe to an additional channel (auto-connects) */
|
|
178
|
+
subscribe: (channel: string, callback?: (data: unknown) => void) => () => void;
|
|
179
|
+
/** Publish data to a channel */
|
|
180
|
+
publish: (channel: string, data: unknown) => void;
|
|
183
181
|
}
|
|
184
182
|
/**
|
|
185
|
-
* Real-time updates hook via WebSocket
|
|
183
|
+
* Real-time updates hook via WebSocket.
|
|
186
184
|
*
|
|
187
|
-
*
|
|
185
|
+
* Uses the base SDK's RealtimeService (shared singleton created in the provider)
|
|
186
|
+
* to ensure correct protocol handling and single WebSocket connection per page.
|
|
188
187
|
*
|
|
189
188
|
* @example
|
|
190
189
|
* ```tsx
|
|
191
|
-
* function
|
|
192
|
-
* const { status,
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* refreshFiles()
|
|
199
|
-
* })
|
|
200
|
-
*
|
|
201
|
-
* return () => unsubscribe()
|
|
202
|
-
* }, [subscribe])
|
|
190
|
+
* function ChatNotifications() {
|
|
191
|
+
* const { status, lastMessage } = useRealtime({
|
|
192
|
+
* channels: ['chat:room-1', 'notifications'],
|
|
193
|
+
* onMessage: (channel, data) => {
|
|
194
|
+
* console.log(`${channel}:`, data)
|
|
195
|
+
* },
|
|
196
|
+
* })
|
|
203
197
|
*
|
|
204
|
-
* return
|
|
205
|
-
* <div>
|
|
206
|
-
* <span>Status: {status}</span>
|
|
207
|
-
* </div>
|
|
208
|
-
* )
|
|
198
|
+
* return <div>Status: {status}</div>
|
|
209
199
|
* }
|
|
210
200
|
* ```
|
|
211
201
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
5
4
|
var sdk = require('@scalemule/sdk');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
7
7
|
// src/provider.tsx
|
|
8
8
|
|
|
@@ -836,6 +836,24 @@ function ScaleMuleProvider({
|
|
|
836
836
|
}),
|
|
837
837
|
[apiKey, applicationId, environment, gatewayUrl, debug, storage, authProxyUrl]
|
|
838
838
|
);
|
|
839
|
+
const baseClient = react.useMemo(() => {
|
|
840
|
+
const resolvedGatewayUrl = gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com");
|
|
841
|
+
return new sdk.ScaleMule({
|
|
842
|
+
apiKey,
|
|
843
|
+
applicationId,
|
|
844
|
+
baseUrl: resolvedGatewayUrl,
|
|
845
|
+
environment,
|
|
846
|
+
debug
|
|
847
|
+
});
|
|
848
|
+
}, [apiKey, applicationId, environment, gatewayUrl, debug]);
|
|
849
|
+
react.useEffect(() => {
|
|
850
|
+
const token = client.getSessionToken();
|
|
851
|
+
if (token) {
|
|
852
|
+
baseClient.setAccessToken(token);
|
|
853
|
+
} else {
|
|
854
|
+
baseClient.clearAccessToken();
|
|
855
|
+
}
|
|
856
|
+
}, [client, baseClient, user]);
|
|
839
857
|
react.useEffect(() => {
|
|
840
858
|
let mounted = true;
|
|
841
859
|
async function initialize() {
|
|
@@ -923,6 +941,7 @@ function ScaleMuleProvider({
|
|
|
923
941
|
const value = react.useMemo(
|
|
924
942
|
() => ({
|
|
925
943
|
client,
|
|
944
|
+
realtime: baseClient.realtime,
|
|
926
945
|
user,
|
|
927
946
|
setUser: handleSetUser,
|
|
928
947
|
initializing,
|
|
@@ -935,7 +954,7 @@ function ScaleMuleProvider({
|
|
|
935
954
|
environment: environment || void 0,
|
|
936
955
|
bootstrapFlags
|
|
937
956
|
}),
|
|
938
|
-
[client, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment, bootstrapFlags]
|
|
957
|
+
[client, baseClient, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment, bootstrapFlags]
|
|
939
958
|
);
|
|
940
959
|
return /* @__PURE__ */ jsxRuntime.jsx(ScaleMuleContext.Provider, { value, children });
|
|
941
960
|
}
|
|
@@ -2231,187 +2250,79 @@ function useUser() {
|
|
|
2231
2250
|
[user, loading, localError, update, changePassword, changeEmail, deleteAccount, exportData]
|
|
2232
2251
|
);
|
|
2233
2252
|
}
|
|
2234
|
-
function useRealtime(options
|
|
2235
|
-
const {
|
|
2236
|
-
autoConnect = true,
|
|
2237
|
-
events,
|
|
2238
|
-
autoReconnect = true,
|
|
2239
|
-
maxReconnectAttempts = 5,
|
|
2240
|
-
reconnectDelay = 1e3
|
|
2241
|
-
} = options;
|
|
2242
|
-
const { client, user, setUser } = useScaleMule();
|
|
2253
|
+
function useRealtime(options) {
|
|
2254
|
+
const { realtime } = useScaleMule();
|
|
2243
2255
|
const [status, setStatus] = react.useState("disconnected");
|
|
2244
|
-
const [error, setError] = react.useState(null);
|
|
2245
2256
|
const [lastMessage, setLastMessage] = react.useState(null);
|
|
2246
|
-
const
|
|
2247
|
-
const
|
|
2248
|
-
const
|
|
2249
|
-
const
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
return `${wsUrl}/v1/realtime`;
|
|
2254
|
-
}, [client]);
|
|
2255
|
-
const handleMessage = react.useCallback(
|
|
2256
|
-
(event) => {
|
|
2257
|
-
try {
|
|
2258
|
-
const message = JSON.parse(event.data);
|
|
2259
|
-
setLastMessage(message);
|
|
2260
|
-
switch (message.event) {
|
|
2261
|
-
case "user.updated":
|
|
2262
|
-
if (user && message.data.id === user.id) {
|
|
2263
|
-
setUser(message.data);
|
|
2264
|
-
}
|
|
2265
|
-
break;
|
|
2266
|
-
case "session.expired":
|
|
2267
|
-
client.clearSession();
|
|
2268
|
-
setUser(null);
|
|
2269
|
-
break;
|
|
2270
|
-
}
|
|
2271
|
-
const subscribers = subscribersRef.current.get(message.event);
|
|
2272
|
-
if (subscribers) {
|
|
2273
|
-
subscribers.forEach((callback) => callback(message.data));
|
|
2274
|
-
}
|
|
2275
|
-
const wildcardSubscribers = subscribersRef.current.get("*");
|
|
2276
|
-
if (wildcardSubscribers) {
|
|
2277
|
-
wildcardSubscribers.forEach((callback) => callback(message));
|
|
2278
|
-
}
|
|
2279
|
-
} catch (err) {
|
|
2280
|
-
console.error("[ScaleMule Realtime] Failed to parse message:", err);
|
|
2281
|
-
}
|
|
2282
|
-
},
|
|
2283
|
-
[client, user, setUser]
|
|
2284
|
-
);
|
|
2285
|
-
const connect = react.useCallback(() => {
|
|
2286
|
-
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
if (!user) {
|
|
2290
|
-
setError({ code: "NOT_AUTHENTICATED", message: "Must be logged in to connect" });
|
|
2291
|
-
return;
|
|
2292
|
-
}
|
|
2293
|
-
const applicationId = client.getApplicationId();
|
|
2294
|
-
if (!applicationId) {
|
|
2295
|
-
setError({ code: "MISSING_APP_ID", message: "applicationId is required for realtime features. Add it to your ScaleMuleProvider config." });
|
|
2296
|
-
return;
|
|
2297
|
-
}
|
|
2298
|
-
setStatus("connecting");
|
|
2299
|
-
setError(null);
|
|
2300
|
-
const url = getWebSocketUrl();
|
|
2301
|
-
try {
|
|
2302
|
-
const ws = new WebSocket(url);
|
|
2303
|
-
ws.onopen = () => {
|
|
2304
|
-
const sessionToken = client.getSessionToken();
|
|
2305
|
-
ws.send(JSON.stringify({
|
|
2306
|
-
type: "auth",
|
|
2307
|
-
token: sessionToken,
|
|
2308
|
-
app_id: applicationId
|
|
2309
|
-
}));
|
|
2310
|
-
};
|
|
2311
|
-
ws.onmessage = (event) => {
|
|
2312
|
-
try {
|
|
2313
|
-
const message = JSON.parse(event.data);
|
|
2314
|
-
if (message.type === "auth_success") {
|
|
2315
|
-
setStatus("connected");
|
|
2316
|
-
reconnectAttempts.current = 0;
|
|
2317
|
-
if (events && events.length > 0) {
|
|
2318
|
-
ws.send(JSON.stringify({ type: "subscribe", events }));
|
|
2319
|
-
}
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
if (message.type === "error") {
|
|
2323
|
-
setError({ code: "AUTH_ERROR", message: message.message || "Authentication failed" });
|
|
2324
|
-
setStatus("disconnected");
|
|
2325
|
-
ws.close(1e3);
|
|
2326
|
-
return;
|
|
2327
|
-
}
|
|
2328
|
-
handleMessage(event);
|
|
2329
|
-
} catch (err) {
|
|
2330
|
-
console.error("[ScaleMule Realtime] Failed to parse message:", err);
|
|
2331
|
-
}
|
|
2332
|
-
};
|
|
2333
|
-
ws.onerror = () => {
|
|
2334
|
-
setError({ code: "WEBSOCKET_ERROR", message: "Connection error" });
|
|
2335
|
-
};
|
|
2336
|
-
ws.onclose = (event) => {
|
|
2337
|
-
setStatus("disconnected");
|
|
2338
|
-
wsRef.current = null;
|
|
2339
|
-
if (autoReconnect && event.code !== 1e3 && reconnectAttempts.current < maxReconnectAttempts) {
|
|
2340
|
-
setStatus("reconnecting");
|
|
2341
|
-
const delay = reconnectDelay * Math.pow(2, reconnectAttempts.current);
|
|
2342
|
-
reconnectAttempts.current++;
|
|
2343
|
-
reconnectTimeout.current = setTimeout(() => {
|
|
2344
|
-
connect();
|
|
2345
|
-
}, delay);
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
wsRef.current = ws;
|
|
2349
|
-
} catch (err) {
|
|
2350
|
-
setError({
|
|
2351
|
-
code: "WEBSOCKET_CONNECT_FAILED",
|
|
2352
|
-
message: err instanceof Error ? err.message : "Failed to connect"
|
|
2353
|
-
});
|
|
2354
|
-
setStatus("disconnected");
|
|
2355
|
-
}
|
|
2356
|
-
}, [user, client, getWebSocketUrl, events, handleMessage, autoReconnect, maxReconnectAttempts, reconnectDelay]);
|
|
2257
|
+
const manualUnsubscribesRef = react.useRef([]);
|
|
2258
|
+
const autoUnsubscribesRef = react.useRef([]);
|
|
2259
|
+
const onMessageRef = react.useRef(void 0);
|
|
2260
|
+
const channelSignature = (options?.channels ?? []).join("");
|
|
2261
|
+
react.useEffect(() => {
|
|
2262
|
+
onMessageRef.current = options?.onMessage;
|
|
2263
|
+
}, [options?.onMessage]);
|
|
2357
2264
|
const disconnect = react.useCallback(() => {
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
reconnectTimeout.current = null;
|
|
2361
|
-
}
|
|
2362
|
-
if (wsRef.current) {
|
|
2363
|
-
wsRef.current.close(1e3, "Client disconnect");
|
|
2364
|
-
wsRef.current = null;
|
|
2365
|
-
}
|
|
2366
|
-
setStatus("disconnected");
|
|
2367
|
-
reconnectAttempts.current = 0;
|
|
2368
|
-
}, []);
|
|
2265
|
+
realtime.disconnect();
|
|
2266
|
+
}, [realtime]);
|
|
2369
2267
|
const subscribe = react.useCallback(
|
|
2370
|
-
(
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2268
|
+
(channel, callback) => {
|
|
2269
|
+
const unsub = realtime.subscribe(channel, (data) => {
|
|
2270
|
+
setLastMessage({ channel, data });
|
|
2271
|
+
callback?.(data);
|
|
2272
|
+
onMessageRef.current?.(channel, data);
|
|
2273
|
+
});
|
|
2274
|
+
manualUnsubscribesRef.current.push(unsub);
|
|
2376
2275
|
return () => {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
subscribers.delete(typedCallback);
|
|
2380
|
-
if (subscribers.size === 0) {
|
|
2381
|
-
subscribersRef.current.delete(event);
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2276
|
+
manualUnsubscribesRef.current = manualUnsubscribesRef.current.filter((fn) => fn !== unsub);
|
|
2277
|
+
unsub();
|
|
2384
2278
|
};
|
|
2385
2279
|
},
|
|
2386
|
-
[]
|
|
2280
|
+
[realtime]
|
|
2387
2281
|
);
|
|
2388
|
-
const
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2282
|
+
const publish = react.useCallback(
|
|
2283
|
+
(channel, data) => {
|
|
2284
|
+
realtime.publish(channel, data);
|
|
2285
|
+
},
|
|
2286
|
+
[realtime]
|
|
2287
|
+
);
|
|
2288
|
+
react.useEffect(() => {
|
|
2289
|
+
const unsub = realtime.onStatusChange((newStatus) => {
|
|
2290
|
+
setStatus(newStatus);
|
|
2291
|
+
});
|
|
2292
|
+
return unsub;
|
|
2293
|
+
}, [realtime]);
|
|
2395
2294
|
react.useEffect(() => {
|
|
2396
|
-
|
|
2397
|
-
|
|
2295
|
+
for (const unsub of autoUnsubscribesRef.current) {
|
|
2296
|
+
unsub();
|
|
2297
|
+
}
|
|
2298
|
+
autoUnsubscribesRef.current = [];
|
|
2299
|
+
for (const channel of options?.channels ?? []) {
|
|
2300
|
+
const unsub = realtime.subscribe(channel, (data) => {
|
|
2301
|
+
setLastMessage({ channel, data });
|
|
2302
|
+
onMessageRef.current?.(channel, data);
|
|
2303
|
+
});
|
|
2304
|
+
autoUnsubscribesRef.current.push(unsub);
|
|
2398
2305
|
}
|
|
2399
2306
|
return () => {
|
|
2400
|
-
|
|
2307
|
+
for (const unsub of autoUnsubscribesRef.current) {
|
|
2308
|
+
unsub();
|
|
2309
|
+
}
|
|
2310
|
+
autoUnsubscribesRef.current = [];
|
|
2401
2311
|
};
|
|
2402
|
-
}, [
|
|
2403
|
-
|
|
2404
|
-
() =>
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
);
|
|
2312
|
+
}, [realtime, channelSignature]);
|
|
2313
|
+
react.useEffect(() => {
|
|
2314
|
+
return () => {
|
|
2315
|
+
for (const unsub of manualUnsubscribesRef.current) {
|
|
2316
|
+
unsub();
|
|
2317
|
+
}
|
|
2318
|
+
manualUnsubscribesRef.current = [];
|
|
2319
|
+
for (const unsub of autoUnsubscribesRef.current) {
|
|
2320
|
+
unsub();
|
|
2321
|
+
}
|
|
2322
|
+
autoUnsubscribesRef.current = [];
|
|
2323
|
+
};
|
|
2324
|
+
}, []);
|
|
2325
|
+
return { status, lastMessage, disconnect, subscribe, publish };
|
|
2415
2326
|
}
|
|
2416
2327
|
|
|
2417
2328
|
// src/hooks/event-dedup.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useState, useMemo, useEffect, useCallback, useContext, useRef } from 'react';
|
|
2
|
+
import { ScaleMule, WebPushManager } from '@scalemule/sdk';
|
|
2
3
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { WebPushManager } from '@scalemule/sdk';
|
|
4
4
|
|
|
5
5
|
// src/provider.tsx
|
|
6
6
|
|
|
@@ -834,6 +834,24 @@ function ScaleMuleProvider({
|
|
|
834
834
|
}),
|
|
835
835
|
[apiKey, applicationId, environment, gatewayUrl, debug, storage, authProxyUrl]
|
|
836
836
|
);
|
|
837
|
+
const baseClient = useMemo(() => {
|
|
838
|
+
const resolvedGatewayUrl = gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com");
|
|
839
|
+
return new ScaleMule({
|
|
840
|
+
apiKey,
|
|
841
|
+
applicationId,
|
|
842
|
+
baseUrl: resolvedGatewayUrl,
|
|
843
|
+
environment,
|
|
844
|
+
debug
|
|
845
|
+
});
|
|
846
|
+
}, [apiKey, applicationId, environment, gatewayUrl, debug]);
|
|
847
|
+
useEffect(() => {
|
|
848
|
+
const token = client.getSessionToken();
|
|
849
|
+
if (token) {
|
|
850
|
+
baseClient.setAccessToken(token);
|
|
851
|
+
} else {
|
|
852
|
+
baseClient.clearAccessToken();
|
|
853
|
+
}
|
|
854
|
+
}, [client, baseClient, user]);
|
|
837
855
|
useEffect(() => {
|
|
838
856
|
let mounted = true;
|
|
839
857
|
async function initialize() {
|
|
@@ -921,6 +939,7 @@ function ScaleMuleProvider({
|
|
|
921
939
|
const value = useMemo(
|
|
922
940
|
() => ({
|
|
923
941
|
client,
|
|
942
|
+
realtime: baseClient.realtime,
|
|
924
943
|
user,
|
|
925
944
|
setUser: handleSetUser,
|
|
926
945
|
initializing,
|
|
@@ -933,7 +952,7 @@ function ScaleMuleProvider({
|
|
|
933
952
|
environment: environment || void 0,
|
|
934
953
|
bootstrapFlags
|
|
935
954
|
}),
|
|
936
|
-
[client, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment, bootstrapFlags]
|
|
955
|
+
[client, baseClient, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment, bootstrapFlags]
|
|
937
956
|
);
|
|
938
957
|
return /* @__PURE__ */ jsx(ScaleMuleContext.Provider, { value, children });
|
|
939
958
|
}
|
|
@@ -2229,187 +2248,79 @@ function useUser() {
|
|
|
2229
2248
|
[user, loading, localError, update, changePassword, changeEmail, deleteAccount, exportData]
|
|
2230
2249
|
);
|
|
2231
2250
|
}
|
|
2232
|
-
function useRealtime(options
|
|
2233
|
-
const {
|
|
2234
|
-
autoConnect = true,
|
|
2235
|
-
events,
|
|
2236
|
-
autoReconnect = true,
|
|
2237
|
-
maxReconnectAttempts = 5,
|
|
2238
|
-
reconnectDelay = 1e3
|
|
2239
|
-
} = options;
|
|
2240
|
-
const { client, user, setUser } = useScaleMule();
|
|
2251
|
+
function useRealtime(options) {
|
|
2252
|
+
const { realtime } = useScaleMule();
|
|
2241
2253
|
const [status, setStatus] = useState("disconnected");
|
|
2242
|
-
const [error, setError] = useState(null);
|
|
2243
2254
|
const [lastMessage, setLastMessage] = useState(null);
|
|
2244
|
-
const
|
|
2245
|
-
const
|
|
2246
|
-
const
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
return `${wsUrl}/v1/realtime`;
|
|
2252
|
-
}, [client]);
|
|
2253
|
-
const handleMessage = useCallback(
|
|
2254
|
-
(event) => {
|
|
2255
|
-
try {
|
|
2256
|
-
const message = JSON.parse(event.data);
|
|
2257
|
-
setLastMessage(message);
|
|
2258
|
-
switch (message.event) {
|
|
2259
|
-
case "user.updated":
|
|
2260
|
-
if (user && message.data.id === user.id) {
|
|
2261
|
-
setUser(message.data);
|
|
2262
|
-
}
|
|
2263
|
-
break;
|
|
2264
|
-
case "session.expired":
|
|
2265
|
-
client.clearSession();
|
|
2266
|
-
setUser(null);
|
|
2267
|
-
break;
|
|
2268
|
-
}
|
|
2269
|
-
const subscribers = subscribersRef.current.get(message.event);
|
|
2270
|
-
if (subscribers) {
|
|
2271
|
-
subscribers.forEach((callback) => callback(message.data));
|
|
2272
|
-
}
|
|
2273
|
-
const wildcardSubscribers = subscribersRef.current.get("*");
|
|
2274
|
-
if (wildcardSubscribers) {
|
|
2275
|
-
wildcardSubscribers.forEach((callback) => callback(message));
|
|
2276
|
-
}
|
|
2277
|
-
} catch (err) {
|
|
2278
|
-
console.error("[ScaleMule Realtime] Failed to parse message:", err);
|
|
2279
|
-
}
|
|
2280
|
-
},
|
|
2281
|
-
[client, user, setUser]
|
|
2282
|
-
);
|
|
2283
|
-
const connect = useCallback(() => {
|
|
2284
|
-
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
2285
|
-
return;
|
|
2286
|
-
}
|
|
2287
|
-
if (!user) {
|
|
2288
|
-
setError({ code: "NOT_AUTHENTICATED", message: "Must be logged in to connect" });
|
|
2289
|
-
return;
|
|
2290
|
-
}
|
|
2291
|
-
const applicationId = client.getApplicationId();
|
|
2292
|
-
if (!applicationId) {
|
|
2293
|
-
setError({ code: "MISSING_APP_ID", message: "applicationId is required for realtime features. Add it to your ScaleMuleProvider config." });
|
|
2294
|
-
return;
|
|
2295
|
-
}
|
|
2296
|
-
setStatus("connecting");
|
|
2297
|
-
setError(null);
|
|
2298
|
-
const url = getWebSocketUrl();
|
|
2299
|
-
try {
|
|
2300
|
-
const ws = new WebSocket(url);
|
|
2301
|
-
ws.onopen = () => {
|
|
2302
|
-
const sessionToken = client.getSessionToken();
|
|
2303
|
-
ws.send(JSON.stringify({
|
|
2304
|
-
type: "auth",
|
|
2305
|
-
token: sessionToken,
|
|
2306
|
-
app_id: applicationId
|
|
2307
|
-
}));
|
|
2308
|
-
};
|
|
2309
|
-
ws.onmessage = (event) => {
|
|
2310
|
-
try {
|
|
2311
|
-
const message = JSON.parse(event.data);
|
|
2312
|
-
if (message.type === "auth_success") {
|
|
2313
|
-
setStatus("connected");
|
|
2314
|
-
reconnectAttempts.current = 0;
|
|
2315
|
-
if (events && events.length > 0) {
|
|
2316
|
-
ws.send(JSON.stringify({ type: "subscribe", events }));
|
|
2317
|
-
}
|
|
2318
|
-
return;
|
|
2319
|
-
}
|
|
2320
|
-
if (message.type === "error") {
|
|
2321
|
-
setError({ code: "AUTH_ERROR", message: message.message || "Authentication failed" });
|
|
2322
|
-
setStatus("disconnected");
|
|
2323
|
-
ws.close(1e3);
|
|
2324
|
-
return;
|
|
2325
|
-
}
|
|
2326
|
-
handleMessage(event);
|
|
2327
|
-
} catch (err) {
|
|
2328
|
-
console.error("[ScaleMule Realtime] Failed to parse message:", err);
|
|
2329
|
-
}
|
|
2330
|
-
};
|
|
2331
|
-
ws.onerror = () => {
|
|
2332
|
-
setError({ code: "WEBSOCKET_ERROR", message: "Connection error" });
|
|
2333
|
-
};
|
|
2334
|
-
ws.onclose = (event) => {
|
|
2335
|
-
setStatus("disconnected");
|
|
2336
|
-
wsRef.current = null;
|
|
2337
|
-
if (autoReconnect && event.code !== 1e3 && reconnectAttempts.current < maxReconnectAttempts) {
|
|
2338
|
-
setStatus("reconnecting");
|
|
2339
|
-
const delay = reconnectDelay * Math.pow(2, reconnectAttempts.current);
|
|
2340
|
-
reconnectAttempts.current++;
|
|
2341
|
-
reconnectTimeout.current = setTimeout(() => {
|
|
2342
|
-
connect();
|
|
2343
|
-
}, delay);
|
|
2344
|
-
}
|
|
2345
|
-
};
|
|
2346
|
-
wsRef.current = ws;
|
|
2347
|
-
} catch (err) {
|
|
2348
|
-
setError({
|
|
2349
|
-
code: "WEBSOCKET_CONNECT_FAILED",
|
|
2350
|
-
message: err instanceof Error ? err.message : "Failed to connect"
|
|
2351
|
-
});
|
|
2352
|
-
setStatus("disconnected");
|
|
2353
|
-
}
|
|
2354
|
-
}, [user, client, getWebSocketUrl, events, handleMessage, autoReconnect, maxReconnectAttempts, reconnectDelay]);
|
|
2255
|
+
const manualUnsubscribesRef = useRef([]);
|
|
2256
|
+
const autoUnsubscribesRef = useRef([]);
|
|
2257
|
+
const onMessageRef = useRef(void 0);
|
|
2258
|
+
const channelSignature = (options?.channels ?? []).join("");
|
|
2259
|
+
useEffect(() => {
|
|
2260
|
+
onMessageRef.current = options?.onMessage;
|
|
2261
|
+
}, [options?.onMessage]);
|
|
2355
2262
|
const disconnect = useCallback(() => {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
reconnectTimeout.current = null;
|
|
2359
|
-
}
|
|
2360
|
-
if (wsRef.current) {
|
|
2361
|
-
wsRef.current.close(1e3, "Client disconnect");
|
|
2362
|
-
wsRef.current = null;
|
|
2363
|
-
}
|
|
2364
|
-
setStatus("disconnected");
|
|
2365
|
-
reconnectAttempts.current = 0;
|
|
2366
|
-
}, []);
|
|
2263
|
+
realtime.disconnect();
|
|
2264
|
+
}, [realtime]);
|
|
2367
2265
|
const subscribe = useCallback(
|
|
2368
|
-
(
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2266
|
+
(channel, callback) => {
|
|
2267
|
+
const unsub = realtime.subscribe(channel, (data) => {
|
|
2268
|
+
setLastMessage({ channel, data });
|
|
2269
|
+
callback?.(data);
|
|
2270
|
+
onMessageRef.current?.(channel, data);
|
|
2271
|
+
});
|
|
2272
|
+
manualUnsubscribesRef.current.push(unsub);
|
|
2374
2273
|
return () => {
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
subscribers.delete(typedCallback);
|
|
2378
|
-
if (subscribers.size === 0) {
|
|
2379
|
-
subscribersRef.current.delete(event);
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2274
|
+
manualUnsubscribesRef.current = manualUnsubscribesRef.current.filter((fn) => fn !== unsub);
|
|
2275
|
+
unsub();
|
|
2382
2276
|
};
|
|
2383
2277
|
},
|
|
2384
|
-
[]
|
|
2278
|
+
[realtime]
|
|
2385
2279
|
);
|
|
2386
|
-
const
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2280
|
+
const publish = useCallback(
|
|
2281
|
+
(channel, data) => {
|
|
2282
|
+
realtime.publish(channel, data);
|
|
2283
|
+
},
|
|
2284
|
+
[realtime]
|
|
2285
|
+
);
|
|
2286
|
+
useEffect(() => {
|
|
2287
|
+
const unsub = realtime.onStatusChange((newStatus) => {
|
|
2288
|
+
setStatus(newStatus);
|
|
2289
|
+
});
|
|
2290
|
+
return unsub;
|
|
2291
|
+
}, [realtime]);
|
|
2393
2292
|
useEffect(() => {
|
|
2394
|
-
|
|
2395
|
-
|
|
2293
|
+
for (const unsub of autoUnsubscribesRef.current) {
|
|
2294
|
+
unsub();
|
|
2295
|
+
}
|
|
2296
|
+
autoUnsubscribesRef.current = [];
|
|
2297
|
+
for (const channel of options?.channels ?? []) {
|
|
2298
|
+
const unsub = realtime.subscribe(channel, (data) => {
|
|
2299
|
+
setLastMessage({ channel, data });
|
|
2300
|
+
onMessageRef.current?.(channel, data);
|
|
2301
|
+
});
|
|
2302
|
+
autoUnsubscribesRef.current.push(unsub);
|
|
2396
2303
|
}
|
|
2397
2304
|
return () => {
|
|
2398
|
-
|
|
2305
|
+
for (const unsub of autoUnsubscribesRef.current) {
|
|
2306
|
+
unsub();
|
|
2307
|
+
}
|
|
2308
|
+
autoUnsubscribesRef.current = [];
|
|
2399
2309
|
};
|
|
2400
|
-
}, [
|
|
2401
|
-
|
|
2402
|
-
() =>
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
);
|
|
2310
|
+
}, [realtime, channelSignature]);
|
|
2311
|
+
useEffect(() => {
|
|
2312
|
+
return () => {
|
|
2313
|
+
for (const unsub of manualUnsubscribesRef.current) {
|
|
2314
|
+
unsub();
|
|
2315
|
+
}
|
|
2316
|
+
manualUnsubscribesRef.current = [];
|
|
2317
|
+
for (const unsub of autoUnsubscribesRef.current) {
|
|
2318
|
+
unsub();
|
|
2319
|
+
}
|
|
2320
|
+
autoUnsubscribesRef.current = [];
|
|
2321
|
+
};
|
|
2322
|
+
}, []);
|
|
2323
|
+
return { status, lastMessage, disconnect, subscribe, publish };
|
|
2413
2324
|
}
|
|
2414
2325
|
|
|
2415
2326
|
// src/hooks/event-dedup.ts
|
package/dist/server/index.d.mts
CHANGED
|
@@ -231,7 +231,7 @@ interface AuthRoutesConfig {
|
|
|
231
231
|
email: string;
|
|
232
232
|
}) => void | Promise<void>;
|
|
233
233
|
}
|
|
234
|
-
type RouteHandler$
|
|
234
|
+
type RouteHandler$2 = (request: Request, context: {
|
|
235
235
|
params: Promise<{
|
|
236
236
|
scalemule?: string[];
|
|
237
237
|
}>;
|
|
@@ -264,10 +264,10 @@ type RouteHandler$1 = (request: Request, context: {
|
|
|
264
264
|
* - POST /api/auth/verify-email - Verify email
|
|
265
265
|
*/
|
|
266
266
|
declare function createAuthRoutes(config?: AuthRoutesConfig): {
|
|
267
|
-
GET: RouteHandler$
|
|
268
|
-
POST: RouteHandler$
|
|
269
|
-
DELETE: RouteHandler$
|
|
270
|
-
PATCH: RouteHandler$
|
|
267
|
+
GET: RouteHandler$2;
|
|
268
|
+
POST: RouteHandler$2;
|
|
269
|
+
DELETE: RouteHandler$2;
|
|
270
|
+
PATCH: RouteHandler$2;
|
|
271
271
|
};
|
|
272
272
|
interface AnalyticsTrackingGateConfig {
|
|
273
273
|
/** Feature flag key used to decide whether to forward analytics */
|
|
@@ -346,7 +346,7 @@ interface AnalyticsRoutesConfig {
|
|
|
346
346
|
* ```
|
|
347
347
|
*/
|
|
348
348
|
declare function createAnalyticsRoutes(config?: AnalyticsRoutesConfig): {
|
|
349
|
-
POST: RouteHandler$
|
|
349
|
+
POST: RouteHandler$2;
|
|
350
350
|
};
|
|
351
351
|
|
|
352
352
|
/**
|
|
@@ -374,15 +374,49 @@ interface PushRoutesConfig {
|
|
|
374
374
|
/** Enable CSRF validation on POST/PUT/DELETE (default: true) */
|
|
375
375
|
csrf?: boolean;
|
|
376
376
|
}
|
|
377
|
-
type RouteHandler = (request: Request, context: {
|
|
377
|
+
type RouteHandler$1 = (request: Request, context: {
|
|
378
378
|
params: Promise<{
|
|
379
379
|
action?: string[];
|
|
380
380
|
}>;
|
|
381
381
|
}) => Promise<Response>;
|
|
382
382
|
declare function createPushRoutes(config: PushRoutesConfig): {
|
|
383
|
+
GET: RouteHandler$1;
|
|
384
|
+
POST: RouteHandler$1;
|
|
385
|
+
PUT: RouteHandler$1;
|
|
386
|
+
DELETE: RouteHandler$1;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Notification Proxy Routes
|
|
391
|
+
*
|
|
392
|
+
* Drop-in route handlers for Next.js App Router that proxy notification
|
|
393
|
+
* requests to the ScaleMule gateway. Follows the same pattern as createPushRoutes().
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```ts
|
|
397
|
+
* // app/api/notifications/[...action]/route.ts
|
|
398
|
+
* import { createNotificationRoutes } from '@scalemule/nextjs/server'
|
|
399
|
+
*
|
|
400
|
+
* export const { GET, PATCH, DELETE } = createNotificationRoutes({
|
|
401
|
+
* apiKey: process.env.SCALEMULE_API_KEY!,
|
|
402
|
+
* gatewayUrl: process.env.SCALEMULE_API_URL!,
|
|
403
|
+
* })
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
interface NotificationRoutesConfig {
|
|
407
|
+
/** Server-side secret API key */
|
|
408
|
+
apiKey: string;
|
|
409
|
+
/** Gateway URL (e.g., https://api.scalemule.com) */
|
|
410
|
+
gatewayUrl: string;
|
|
411
|
+
}
|
|
412
|
+
type RouteHandler = (request: Request, context: {
|
|
413
|
+
params: Promise<{
|
|
414
|
+
action?: string[];
|
|
415
|
+
}>;
|
|
416
|
+
}) => Promise<Response>;
|
|
417
|
+
declare function createNotificationRoutes(config: NotificationRoutesConfig): {
|
|
383
418
|
GET: RouteHandler;
|
|
384
|
-
|
|
385
|
-
PUT: RouteHandler;
|
|
419
|
+
PATCH: RouteHandler;
|
|
386
420
|
DELETE: RouteHandler;
|
|
387
421
|
};
|
|
388
422
|
|
|
@@ -977,4 +1011,4 @@ declare function invalidateBundleCache(key?: string): void;
|
|
|
977
1011
|
*/
|
|
978
1012
|
declare function prefetchBundles(keys: string[]): Promise<void>;
|
|
979
1013
|
|
|
980
|
-
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
1014
|
+
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -231,7 +231,7 @@ interface AuthRoutesConfig {
|
|
|
231
231
|
email: string;
|
|
232
232
|
}) => void | Promise<void>;
|
|
233
233
|
}
|
|
234
|
-
type RouteHandler$
|
|
234
|
+
type RouteHandler$2 = (request: Request, context: {
|
|
235
235
|
params: Promise<{
|
|
236
236
|
scalemule?: string[];
|
|
237
237
|
}>;
|
|
@@ -264,10 +264,10 @@ type RouteHandler$1 = (request: Request, context: {
|
|
|
264
264
|
* - POST /api/auth/verify-email - Verify email
|
|
265
265
|
*/
|
|
266
266
|
declare function createAuthRoutes(config?: AuthRoutesConfig): {
|
|
267
|
-
GET: RouteHandler$
|
|
268
|
-
POST: RouteHandler$
|
|
269
|
-
DELETE: RouteHandler$
|
|
270
|
-
PATCH: RouteHandler$
|
|
267
|
+
GET: RouteHandler$2;
|
|
268
|
+
POST: RouteHandler$2;
|
|
269
|
+
DELETE: RouteHandler$2;
|
|
270
|
+
PATCH: RouteHandler$2;
|
|
271
271
|
};
|
|
272
272
|
interface AnalyticsTrackingGateConfig {
|
|
273
273
|
/** Feature flag key used to decide whether to forward analytics */
|
|
@@ -346,7 +346,7 @@ interface AnalyticsRoutesConfig {
|
|
|
346
346
|
* ```
|
|
347
347
|
*/
|
|
348
348
|
declare function createAnalyticsRoutes(config?: AnalyticsRoutesConfig): {
|
|
349
|
-
POST: RouteHandler$
|
|
349
|
+
POST: RouteHandler$2;
|
|
350
350
|
};
|
|
351
351
|
|
|
352
352
|
/**
|
|
@@ -374,15 +374,49 @@ interface PushRoutesConfig {
|
|
|
374
374
|
/** Enable CSRF validation on POST/PUT/DELETE (default: true) */
|
|
375
375
|
csrf?: boolean;
|
|
376
376
|
}
|
|
377
|
-
type RouteHandler = (request: Request, context: {
|
|
377
|
+
type RouteHandler$1 = (request: Request, context: {
|
|
378
378
|
params: Promise<{
|
|
379
379
|
action?: string[];
|
|
380
380
|
}>;
|
|
381
381
|
}) => Promise<Response>;
|
|
382
382
|
declare function createPushRoutes(config: PushRoutesConfig): {
|
|
383
|
+
GET: RouteHandler$1;
|
|
384
|
+
POST: RouteHandler$1;
|
|
385
|
+
PUT: RouteHandler$1;
|
|
386
|
+
DELETE: RouteHandler$1;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Notification Proxy Routes
|
|
391
|
+
*
|
|
392
|
+
* Drop-in route handlers for Next.js App Router that proxy notification
|
|
393
|
+
* requests to the ScaleMule gateway. Follows the same pattern as createPushRoutes().
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```ts
|
|
397
|
+
* // app/api/notifications/[...action]/route.ts
|
|
398
|
+
* import { createNotificationRoutes } from '@scalemule/nextjs/server'
|
|
399
|
+
*
|
|
400
|
+
* export const { GET, PATCH, DELETE } = createNotificationRoutes({
|
|
401
|
+
* apiKey: process.env.SCALEMULE_API_KEY!,
|
|
402
|
+
* gatewayUrl: process.env.SCALEMULE_API_URL!,
|
|
403
|
+
* })
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
interface NotificationRoutesConfig {
|
|
407
|
+
/** Server-side secret API key */
|
|
408
|
+
apiKey: string;
|
|
409
|
+
/** Gateway URL (e.g., https://api.scalemule.com) */
|
|
410
|
+
gatewayUrl: string;
|
|
411
|
+
}
|
|
412
|
+
type RouteHandler = (request: Request, context: {
|
|
413
|
+
params: Promise<{
|
|
414
|
+
action?: string[];
|
|
415
|
+
}>;
|
|
416
|
+
}) => Promise<Response>;
|
|
417
|
+
declare function createNotificationRoutes(config: NotificationRoutesConfig): {
|
|
383
418
|
GET: RouteHandler;
|
|
384
|
-
|
|
385
|
-
PUT: RouteHandler;
|
|
419
|
+
PATCH: RouteHandler;
|
|
386
420
|
DELETE: RouteHandler;
|
|
387
421
|
};
|
|
388
422
|
|
|
@@ -977,4 +1011,4 @@ declare function invalidateBundleCache(key?: string): void;
|
|
|
977
1011
|
*/
|
|
978
1012
|
declare function prefetchBundles(keys: string[]): Promise<void>;
|
|
979
1013
|
|
|
980
|
-
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
1014
|
+
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.js
CHANGED
|
@@ -1732,6 +1732,73 @@ function createPushRoutes(config) {
|
|
|
1732
1732
|
return { GET, POST, PUT, DELETE };
|
|
1733
1733
|
}
|
|
1734
1734
|
|
|
1735
|
+
// src/server/notifications.ts
|
|
1736
|
+
function createNotificationRoutes(config) {
|
|
1737
|
+
const { apiKey, gatewayUrl } = config;
|
|
1738
|
+
async function proxyToGateway(request, method, subPath) {
|
|
1739
|
+
const targetUrl = `${gatewayUrl}/v1/notifications/${subPath}`;
|
|
1740
|
+
const headers2 = {
|
|
1741
|
+
"x-api-key": apiKey,
|
|
1742
|
+
"Content-Type": "application/json"
|
|
1743
|
+
};
|
|
1744
|
+
const session = getSessionFromRequest(request);
|
|
1745
|
+
if (session?.sessionToken) {
|
|
1746
|
+
headers2["Authorization"] = `Bearer ${session.sessionToken}`;
|
|
1747
|
+
}
|
|
1748
|
+
const clientContext = extractClientContext(
|
|
1749
|
+
request
|
|
1750
|
+
);
|
|
1751
|
+
const contextHeaders = buildClientContextHeaders(clientContext);
|
|
1752
|
+
Object.assign(headers2, contextHeaders);
|
|
1753
|
+
const workspaceId = request.headers.get("x-sm-workspace-id");
|
|
1754
|
+
if (workspaceId) {
|
|
1755
|
+
headers2["x-sm-workspace-id"] = workspaceId;
|
|
1756
|
+
}
|
|
1757
|
+
const fetchOptions = {
|
|
1758
|
+
method,
|
|
1759
|
+
headers: headers2
|
|
1760
|
+
};
|
|
1761
|
+
if (method === "PATCH") {
|
|
1762
|
+
try {
|
|
1763
|
+
const body = await request.text();
|
|
1764
|
+
if (body) {
|
|
1765
|
+
fetchOptions.body = body;
|
|
1766
|
+
}
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
let targetUrlWithQuery = targetUrl;
|
|
1771
|
+
if (method === "GET") {
|
|
1772
|
+
const url = new URL(request.url);
|
|
1773
|
+
if (url.search) {
|
|
1774
|
+
targetUrlWithQuery = `${targetUrl}${url.search}`;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
const response = await fetch(targetUrlWithQuery, fetchOptions);
|
|
1778
|
+
const responseBody = await response.text();
|
|
1779
|
+
return new Response(responseBody, {
|
|
1780
|
+
status: response.status,
|
|
1781
|
+
headers: { "Content-Type": "application/json" }
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
const GET = async (request, context) => {
|
|
1785
|
+
const params = await context.params;
|
|
1786
|
+
const subPath = params.action?.join("/") || "";
|
|
1787
|
+
return proxyToGateway(request, "GET", subPath);
|
|
1788
|
+
};
|
|
1789
|
+
const PATCH = async (request, context) => {
|
|
1790
|
+
const params = await context.params;
|
|
1791
|
+
const subPath = params.action?.join("/") || "";
|
|
1792
|
+
return proxyToGateway(request, "PATCH", subPath);
|
|
1793
|
+
};
|
|
1794
|
+
const DELETE = async (request, context) => {
|
|
1795
|
+
const params = await context.params;
|
|
1796
|
+
const subPath = params.action?.join("/") || "";
|
|
1797
|
+
return proxyToGateway(request, "DELETE", subPath);
|
|
1798
|
+
};
|
|
1799
|
+
return { GET, PATCH, DELETE };
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1735
1802
|
// src/server/errors.ts
|
|
1736
1803
|
var ScaleMuleError = class extends Error {
|
|
1737
1804
|
constructor(code, message, status = 400, details) {
|
|
@@ -2355,6 +2422,7 @@ exports.configureSecrets = configureSecrets;
|
|
|
2355
2422
|
exports.createAnalyticsRoutes = createAnalyticsRoutes;
|
|
2356
2423
|
exports.createAuthMiddleware = createAuthMiddleware;
|
|
2357
2424
|
exports.createAuthRoutes = createAuthRoutes;
|
|
2425
|
+
exports.createNotificationRoutes = createNotificationRoutes;
|
|
2358
2426
|
exports.createPushRoutes = createPushRoutes;
|
|
2359
2427
|
exports.createServerClient = createServerClient;
|
|
2360
2428
|
exports.createWebhookHandler = createWebhookHandler;
|
package/dist/server/index.mjs
CHANGED
|
@@ -1730,6 +1730,73 @@ function createPushRoutes(config) {
|
|
|
1730
1730
|
return { GET, POST, PUT, DELETE };
|
|
1731
1731
|
}
|
|
1732
1732
|
|
|
1733
|
+
// src/server/notifications.ts
|
|
1734
|
+
function createNotificationRoutes(config) {
|
|
1735
|
+
const { apiKey, gatewayUrl } = config;
|
|
1736
|
+
async function proxyToGateway(request, method, subPath) {
|
|
1737
|
+
const targetUrl = `${gatewayUrl}/v1/notifications/${subPath}`;
|
|
1738
|
+
const headers2 = {
|
|
1739
|
+
"x-api-key": apiKey,
|
|
1740
|
+
"Content-Type": "application/json"
|
|
1741
|
+
};
|
|
1742
|
+
const session = getSessionFromRequest(request);
|
|
1743
|
+
if (session?.sessionToken) {
|
|
1744
|
+
headers2["Authorization"] = `Bearer ${session.sessionToken}`;
|
|
1745
|
+
}
|
|
1746
|
+
const clientContext = extractClientContext(
|
|
1747
|
+
request
|
|
1748
|
+
);
|
|
1749
|
+
const contextHeaders = buildClientContextHeaders(clientContext);
|
|
1750
|
+
Object.assign(headers2, contextHeaders);
|
|
1751
|
+
const workspaceId = request.headers.get("x-sm-workspace-id");
|
|
1752
|
+
if (workspaceId) {
|
|
1753
|
+
headers2["x-sm-workspace-id"] = workspaceId;
|
|
1754
|
+
}
|
|
1755
|
+
const fetchOptions = {
|
|
1756
|
+
method,
|
|
1757
|
+
headers: headers2
|
|
1758
|
+
};
|
|
1759
|
+
if (method === "PATCH") {
|
|
1760
|
+
try {
|
|
1761
|
+
const body = await request.text();
|
|
1762
|
+
if (body) {
|
|
1763
|
+
fetchOptions.body = body;
|
|
1764
|
+
}
|
|
1765
|
+
} catch {
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
let targetUrlWithQuery = targetUrl;
|
|
1769
|
+
if (method === "GET") {
|
|
1770
|
+
const url = new URL(request.url);
|
|
1771
|
+
if (url.search) {
|
|
1772
|
+
targetUrlWithQuery = `${targetUrl}${url.search}`;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
const response = await fetch(targetUrlWithQuery, fetchOptions);
|
|
1776
|
+
const responseBody = await response.text();
|
|
1777
|
+
return new Response(responseBody, {
|
|
1778
|
+
status: response.status,
|
|
1779
|
+
headers: { "Content-Type": "application/json" }
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
const GET = async (request, context) => {
|
|
1783
|
+
const params = await context.params;
|
|
1784
|
+
const subPath = params.action?.join("/") || "";
|
|
1785
|
+
return proxyToGateway(request, "GET", subPath);
|
|
1786
|
+
};
|
|
1787
|
+
const PATCH = async (request, context) => {
|
|
1788
|
+
const params = await context.params;
|
|
1789
|
+
const subPath = params.action?.join("/") || "";
|
|
1790
|
+
return proxyToGateway(request, "PATCH", subPath);
|
|
1791
|
+
};
|
|
1792
|
+
const DELETE = async (request, context) => {
|
|
1793
|
+
const params = await context.params;
|
|
1794
|
+
const subPath = params.action?.join("/") || "";
|
|
1795
|
+
return proxyToGateway(request, "DELETE", subPath);
|
|
1796
|
+
};
|
|
1797
|
+
return { GET, PATCH, DELETE };
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1733
1800
|
// src/server/errors.ts
|
|
1734
1801
|
var ScaleMuleError = class extends Error {
|
|
1735
1802
|
constructor(code, message, status = 400, details) {
|
|
@@ -2336,4 +2403,4 @@ async function prefetchBundles(keys) {
|
|
|
2336
2403
|
await Promise.all(keys.map((key) => getBundle(key)));
|
|
2337
2404
|
}
|
|
2338
2405
|
|
|
2339
|
-
export { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, OAUTH_STATE_COOKIE_NAME, SESSION_COOKIE_NAME, ScaleMuleError, ScaleMuleServer, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createPushRoutes, createServerClient, createWebhookHandler, createWebhookRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, parseWebhookEvent, prefetchBundles, prefetchSecrets, registerVideoWebhook, requireAppSecret, requireBundle, requireSession, resolveGatewayUrl, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, verifyWebhookSignature, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
2406
|
+
export { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, OAUTH_STATE_COOKIE_NAME, SESSION_COOKIE_NAME, ScaleMuleError, ScaleMuleServer, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, createServerClient, createWebhookHandler, createWebhookRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, parseWebhookEvent, prefetchBundles, prefetchSecrets, registerVideoWebhook, requireAppSecret, requireBundle, requireSession, resolveGatewayUrl, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, verifyWebhookSignature, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/nextjs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"clean": "rm -rf dist"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@scalemule/sdk": "^0.0.
|
|
55
|
+
"@scalemule/sdk": "^0.0.27"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"next": ">=14.0.0",
|