@pulse-editor/react-api 0.1.1-beta.83 → 0.1.1-beta.84

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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Hook to access and update persisted settings for a specific app in the editor.
3
+ * @param appId The ID of the app whose settings should be retrieved and updated.
4
+ * @returns `isReady`, the current `settings` object, and an `updateSettings` function.
5
+ */
6
+ export default function useAppSettings(appId: string): {
7
+ isReady: boolean;
8
+ isLoaded: boolean;
9
+ settings: Record<string, any>;
10
+ refetch: () => Promise<void>;
11
+ updateSettings: (newSettings: Record<string, any>) => Promise<void>;
12
+ deleteSetting: (key: string) => Promise<void>;
13
+ };
@@ -0,0 +1,36 @@
1
+ import { OAuthConnectConfig } from "@pulse-editor/shared-utils";
2
+ export type OAuthState = {
3
+ accessToken?: string;
4
+ refreshToken?: string;
5
+ tokenType?: string;
6
+ scope?: string;
7
+ expiresAt?: string | number;
8
+ idToken?: string;
9
+ [key: string]: any;
10
+ };
11
+ /**
12
+ * Hook to manage OAuth credentials for a Pulse App.
13
+ *
14
+ * Handles the full OAuth flow automatically:
15
+ * - Dynamic client registration (RFC 7591) when `registrationEndpoint` is provided
16
+ * - PKCE code challenge generation
17
+ * - Opening the authorization modal
18
+ * - Token exchange and storage (via the Pulse Editor backend)
19
+ *
20
+ * @param appId The ID of the app.
21
+ * @param provider A provider identifier used to namespace the OAuth state.
22
+ */
23
+ export default function useOAuth(appId: string, provider?: string): {
24
+ isReady: boolean;
25
+ isLoading: boolean;
26
+ oauth: OAuthState | null;
27
+ isAuthenticated: boolean;
28
+ connect: (config: OAuthConnectConfig) => Promise<void>;
29
+ disconnect: () => Promise<void>;
30
+ refetchOAuth: () => Promise<void>;
31
+ refreshToken: (params: {
32
+ tokenEndpoint: string;
33
+ clientId: string;
34
+ clientSecret?: string;
35
+ }) => Promise<OAuthState | null>;
36
+ };
@@ -1 +1,9 @@
1
- export default function useSnapShotState<T>(key: string, initialValue?: T, onRestore?: (value: T) => void): readonly [T, import("react").Dispatch<import("react").SetStateAction<T>>];
1
+ /**
2
+ * A hook that syncs state with a snapshot context,
3
+ * allowing for state restoration across component unmounts and remounts.
4
+ * @param key
5
+ * @param initialValue
6
+ * @param onRestore
7
+ * @returns
8
+ */
9
+ export default function useSnapshotState<T>(key: string, initialValue?: T, onRestore?: (value: T) => void): readonly [T, import("react").Dispatch<import("react").SetStateAction<T>>];
package/dist/main.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import useAgentTools from "./hooks/agent/use-agent-tools";
2
2
  import useAgents from "./hooks/agent/use-agents";
3
+ import useActionEffect from "./hooks/editor/use-action-effect";
3
4
  import useLoading from "./hooks/editor/use-loading";
4
5
  import useNotification from "./hooks/editor/use-notification";
5
- import useActionEffect from "./hooks/editor/use-action-effect";
6
6
  import useTheme from "./hooks/editor/use-theme";
7
7
  import useFile from "./hooks/workspace/use-file";
8
8
  import useImageGen from "./hooks/ai-modality/use-image-gen";
@@ -11,11 +11,14 @@ import useOCR from "./hooks/ai-modality/use-ocr";
11
11
  import useSTT from "./hooks/ai-modality/use-stt";
12
12
  import useTTS from "./hooks/ai-modality/use-tts";
13
13
  import useVideoGen from "./hooks/ai-modality/use-video-gen";
14
+ import useAppSettings from "./hooks/editor/use-app-settings";
14
15
  import { useArtifact } from "./hooks/editor/use-artifact";
15
16
  import useEditorEnv from "./hooks/editor/use-editor-env";
16
17
  import useOpenApp from "./hooks/editor/use-open-app";
17
18
  import useOpenLink from "./hooks/editor/use-open-link";
18
19
  import useOwnedAppView from "./hooks/editor/use-owned-app-view";
20
+ import useOAuth from "./hooks/editor/use-oauth";
21
+ export type { OAuthState } from "./hooks/editor/use-oauth";
19
22
  import useSnapshotState from "./hooks/editor/use-snapshot-state";
20
23
  import { useTranslations } from "./hooks/editor/use-translations";
21
24
  import useFileSystem from "./hooks/workspace/use-file-system";
@@ -24,4 +27,4 @@ import useTerminal from "./hooks/workspace/use-terminal";
24
27
  import useWorkspaceInfo from "./hooks/workspace/use-workspace-info";
25
28
  import ReceiveFileProvider from "./providers/receive-file-provider";
26
29
  import SnapshotProvider from "./providers/snapshot-provider";
27
- export { ReceiveFileProvider, SnapshotProvider, useAgents, useAgentTools, useArtifact, useEditorEnv, useFile, useFileSystem, useImageGen, useLLM, useLoading, useNotification, useOCR, useOpenApp, useOpenLink, useOwnedAppView, useReceiveFile, useActionEffect, useSnapshotState, useSTT, useTerminal, useTheme, useTranslations, useTTS, useVideoGen, useWorkspaceInfo, };
30
+ export { ReceiveFileProvider, SnapshotProvider, useActionEffect, useAgents, useAgentTools, useAppSettings, useArtifact, useEditorEnv, useFile, useFileSystem, useImageGen, useLLM, useLoading, useNotification, useOCR, useOpenApp, useOpenLink, useOAuth, useOwnedAppView, useReceiveFile, useSnapshotState, useSTT, useTerminal, useTheme, useTranslations, useTTS, useVideoGen, useWorkspaceInfo, };
package/dist/main.js CHANGED
@@ -492,40 +492,6 @@ function useAgents() {
492
492
  };
493
493
  }
494
494
 
495
- function useLoading() {
496
- const receiverHandlerMap = new Map();
497
- const { imc, isReady } = useIMC(receiverHandlerMap, "loading");
498
- const [isLoading, setIsLoading] = useState(true);
499
- useEffect(() => {
500
- if (isReady) {
501
- imc?.sendMessage(IMCMessageTypeEnum.EditorLoadingApp, {
502
- isLoading,
503
- });
504
- }
505
- }, [isLoading]);
506
- function toggleLoading(isLoading) {
507
- setIsLoading((prev) => isLoading);
508
- }
509
- return {
510
- isReady,
511
- toggleLoading,
512
- };
513
- }
514
-
515
- function useNotification() {
516
- const receiverHandlerMap = new Map();
517
- const { imc, isReady } = useIMC(receiverHandlerMap, "notification");
518
- function openNotification(text, type) {
519
- if (isReady) {
520
- imc?.sendMessage(IMCMessageTypeEnum.EditorShowNotification, {
521
- text,
522
- type,
523
- });
524
- }
525
- }
526
- return { isReady, openNotification };
527
- }
528
-
529
495
  var MANIFEST_EXT = '.json';
530
496
  var BROWSER_LOG_KEY = 'FEDERATION_DEBUG';
531
497
  var NameTransformSymbol = {
@@ -5851,6 +5817,40 @@ function useActionEffect(params, deps, isExtReady = true) {
5851
5817
  };
5852
5818
  }
5853
5819
 
5820
+ function useLoading() {
5821
+ const receiverHandlerMap = new Map();
5822
+ const { imc, isReady } = useIMC(receiverHandlerMap, "loading");
5823
+ const [isLoading, setIsLoading] = useState(true);
5824
+ useEffect(() => {
5825
+ if (isReady) {
5826
+ imc?.sendMessage(IMCMessageTypeEnum.EditorLoadingApp, {
5827
+ isLoading,
5828
+ });
5829
+ }
5830
+ }, [isLoading]);
5831
+ function toggleLoading(isLoading) {
5832
+ setIsLoading((prev) => isLoading);
5833
+ }
5834
+ return {
5835
+ isReady,
5836
+ toggleLoading,
5837
+ };
5838
+ }
5839
+
5840
+ function useNotification() {
5841
+ const receiverHandlerMap = new Map();
5842
+ const { imc, isReady } = useIMC(receiverHandlerMap, "notification");
5843
+ function openNotification(text, type) {
5844
+ if (isReady) {
5845
+ imc?.sendMessage(IMCMessageTypeEnum.EditorShowNotification, {
5846
+ text,
5847
+ type,
5848
+ });
5849
+ }
5850
+ }
5851
+ return { isReady, openNotification };
5852
+ }
5853
+
5854
5854
  function useTheme() {
5855
5855
  const [theme, setTheme] = useState("light");
5856
5856
  const receiverHandlerMap = new Map();
@@ -6182,6 +6182,54 @@ function useVideoGen() {
6182
6182
  };
6183
6183
  }
6184
6184
 
6185
+ /**
6186
+ * Hook to access and update persisted settings for a specific app in the editor.
6187
+ * @param appId The ID of the app whose settings should be retrieved and updated.
6188
+ * @returns `isReady`, the current `settings` object, and an `updateSettings` function.
6189
+ */
6190
+ function useAppSettings(appId) {
6191
+ const receiverHandlerMap = new Map();
6192
+ const { imc, isReady } = useIMC(receiverHandlerMap, "app-settings");
6193
+ const [settings, setSettings] = useState({});
6194
+ const [isLoaded, setIsLoaded] = useState(false);
6195
+ useEffect(() => {
6196
+ if (isReady) {
6197
+ refetch();
6198
+ }
6199
+ }, [isReady]);
6200
+ async function refetch() {
6201
+ const result = await imc?.sendMessage(IMCMessageTypeEnum.EditorGetAppSettings, { appId });
6202
+ setSettings(result ?? {});
6203
+ setIsLoaded(true);
6204
+ }
6205
+ async function updateSettings(newSettings) {
6206
+ await imc?.sendMessage(IMCMessageTypeEnum.EditorSetAppSettings, {
6207
+ appId,
6208
+ settings: newSettings,
6209
+ });
6210
+ setSettings((prev) => ({ ...prev, ...newSettings }));
6211
+ }
6212
+ async function deleteSetting(key) {
6213
+ await imc?.sendMessage(IMCMessageTypeEnum.EditorDeleteAppSetting, {
6214
+ appId,
6215
+ key,
6216
+ });
6217
+ setSettings((prev) => {
6218
+ const next = { ...prev };
6219
+ delete next[key];
6220
+ return next;
6221
+ });
6222
+ }
6223
+ return {
6224
+ isReady,
6225
+ isLoaded,
6226
+ settings,
6227
+ refetch,
6228
+ updateSettings,
6229
+ deleteSetting,
6230
+ };
6231
+ }
6232
+
6185
6233
  function useArtifact() {
6186
6234
  const receiverHandlerMap = new Map();
6187
6235
  const { imc, isReady } = useIMC(receiverHandlerMap, "artifact");
@@ -6285,6 +6333,124 @@ function useOwnedAppView() {
6285
6333
  };
6286
6334
  }
6287
6335
 
6336
+ function generateRandomString(length) {
6337
+ const array = new Uint8Array(length);
6338
+ crypto.getRandomValues(array);
6339
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
6340
+ }
6341
+ /**
6342
+ * Hook to manage OAuth credentials for a Pulse App.
6343
+ *
6344
+ * Handles the full OAuth flow automatically:
6345
+ * - Dynamic client registration (RFC 7591) when `registrationEndpoint` is provided
6346
+ * - PKCE code challenge generation
6347
+ * - Opening the authorization modal
6348
+ * - Token exchange and storage (via the Pulse Editor backend)
6349
+ *
6350
+ * @param appId The ID of the app.
6351
+ * @param provider A provider identifier used to namespace the OAuth state.
6352
+ */
6353
+ function useOAuth(appId, provider = "default") {
6354
+ const { isReady, isLoaded, settings, deleteSetting, refetch } = useAppSettings(appId);
6355
+ const keyPrefix = `oauth:${provider}:`;
6356
+ const oauthEntries = Object.entries(settings).filter(([k]) => k.startsWith(keyPrefix));
6357
+ const oauthRaw = oauthEntries.length > 0
6358
+ ? Object.fromEntries(oauthEntries.map(([k, v]) => [k.slice(keyPrefix.length), v]))
6359
+ : null;
6360
+ const oauth = oauthRaw &&
6361
+ Object.values(oauthRaw).some((v) => v !== null && v !== undefined)
6362
+ ? oauthRaw
6363
+ : null;
6364
+ const receiverHandlerMap = new Map();
6365
+ const { imc, isReady: isIMCReady } = useIMC(receiverHandlerMap, "oauth");
6366
+ /**
6367
+ * Initiate the OAuth flow. The editor handles everything:
6368
+ * - Dynamic client registration (if `registrationEndpoint` is provided and `clientId` is omitted)
6369
+ * - PKCE (`codeVerifier` is auto-generated if not provided)
6370
+ * - Opening the consent modal and authorization window
6371
+ * - Token exchange and storage
6372
+ */
6373
+ async function connect(config) {
6374
+ if (!isIMCReady) {
6375
+ throw new Error("IMC is not ready.");
6376
+ }
6377
+ // Auto-generate PKCE code verifier if not provided
6378
+ const resolvedConfig = { ...config };
6379
+ if (!resolvedConfig.codeVerifier) {
6380
+ resolvedConfig.codeVerifier = generateRandomString(64);
6381
+ resolvedConfig.codeChallengeMethod =
6382
+ resolvedConfig.codeChallengeMethod ?? "S256";
6383
+ }
6384
+ await imc?.sendMessage(IMCMessageTypeEnum.EditorOAuthConnect, {
6385
+ appId,
6386
+ provider,
6387
+ config: resolvedConfig,
6388
+ });
6389
+ }
6390
+ /**
6391
+ * Sign out: removes all stored OAuth tokens for this provider from the
6392
+ * editor backend and resets local auth state immediately.
6393
+ */
6394
+ async function disconnect() {
6395
+ // Collect keys before deletion so we don't rely on stale `oauth`
6396
+ const keysToDelete = oauthEntries.map(([k]) => k);
6397
+ await Promise.all(keysToDelete.map((key) => deleteSetting(key)));
6398
+ }
6399
+ /**
6400
+ * Re-fetch OAuth state from app settings.
6401
+ * Call this after the user completes the OAuth flow in the external window.
6402
+ */
6403
+ async function refetchOAuth() {
6404
+ await refetch();
6405
+ }
6406
+ // Revalidate OAuth state when the user returns to the tab
6407
+ useEffect(() => {
6408
+ function handleVisibilityChange() {
6409
+ if (document.visibilityState === "visible") {
6410
+ refetch();
6411
+ }
6412
+ }
6413
+ document.addEventListener("visibilitychange", handleVisibilityChange);
6414
+ return () => {
6415
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
6416
+ };
6417
+ }, [isReady]);
6418
+ /**
6419
+ * Refresh the access token using the stored refresh token.
6420
+ * The editor backend exchanges the refresh token with the OAuth provider
6421
+ * and persists the new tokens automatically.
6422
+ */
6423
+ async function refreshToken(params) {
6424
+ if (!isIMCReady) {
6425
+ throw new Error("IMC is not ready.");
6426
+ }
6427
+ if (!oauth?.refreshToken) {
6428
+ throw new Error("No refresh token available.");
6429
+ }
6430
+ const result = await imc?.sendMessage(IMCMessageTypeEnum.EditorOAuthRefreshToken, {
6431
+ appId,
6432
+ provider,
6433
+ tokenEndpoint: params.tokenEndpoint,
6434
+ refreshToken: oauth.refreshToken,
6435
+ clientId: params.clientId,
6436
+ clientSecret: params.clientSecret,
6437
+ });
6438
+ // Re-fetch settings to sync local state
6439
+ await refetchOAuth();
6440
+ return result ?? null;
6441
+ }
6442
+ return {
6443
+ isReady,
6444
+ isLoading: !isLoaded,
6445
+ oauth,
6446
+ isAuthenticated: Boolean(oauth?.accessToken),
6447
+ connect,
6448
+ disconnect,
6449
+ refetchOAuth,
6450
+ refreshToken,
6451
+ };
6452
+ }
6453
+
6288
6454
  const SnapshotContext = createContext(undefined);
6289
6455
  function SnapshotProvider({ children, }) {
6290
6456
  const [states, setStates] = useState({});
@@ -6315,7 +6481,15 @@ function SnapshotProvider({ children, }) {
6315
6481
  return (React.createElement(SnapshotContext.Provider, { value: { states, setStates } }, children));
6316
6482
  }
6317
6483
 
6318
- function useSnapShotState(key, initialValue, onRestore) {
6484
+ /**
6485
+ * A hook that syncs state with a snapshot context,
6486
+ * allowing for state restoration across component unmounts and remounts.
6487
+ * @param key
6488
+ * @param initialValue
6489
+ * @param onRestore
6490
+ * @returns
6491
+ */
6492
+ function useSnapshotState(key, initialValue, onRestore) {
6319
6493
  const snapshotContext = useContext(SnapshotContext);
6320
6494
  if (!snapshotContext) {
6321
6495
  throw new Error("useSnapShotState must be used within a SnapshotProvider");
@@ -6543,5 +6717,5 @@ function useWorkspaceInfo() {
6543
6717
  };
6544
6718
  }
6545
6719
 
6546
- export { ReceiveFileProvider, SnapshotProvider, useActionEffect, useAgentTools, useAgents, useArtifact, useEditorEnv, useFile, useFileSystem, useImageGen, useLLM, useLoading, useNotification, useOCR, useOpenApp, useOpenLink, useOwnedAppView, useReceiveFile, useSTT, useSnapShotState as useSnapshotState, useTTS, useTerminal, useTheme, useTranslations, useVideoGen, useWorkspaceInfo };
6720
+ export { ReceiveFileProvider, SnapshotProvider, useActionEffect, useAgentTools, useAgents, useAppSettings, useArtifact, useEditorEnv, useFile, useFileSystem, useImageGen, useLLM, useLoading, useNotification, useOAuth, useOCR, useOpenApp, useOpenLink, useOwnedAppView, useReceiveFile, useSTT, useSnapshotState, useTTS, useTerminal, useTheme, useTranslations, useVideoGen, useWorkspaceInfo };
6547
6721
  //# sourceMappingURL=main.js.map