@agent-native/core 0.12.30 → 0.12.31
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/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +21 -5
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +77 -20
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +9 -8
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +23 -4
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/dev-overlay/DevOverlay.d.ts.map +1 -1
- package/dist/client/dev-overlay/DevOverlay.js +5 -1
- package/dist/client/dev-overlay/DevOverlay.js.map +1 -1
- package/dist/client/guided-questions.d.ts +77 -0
- package/dist/client/guided-questions.d.ts.map +1 -0
- package/dist/client/guided-questions.js +295 -0
- package/dist/client/guided-questions.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +8 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +78 -12
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.d.ts +2 -1
- package/dist/client/settings/SettingsSection.d.ts.map +1 -1
- package/dist/client/settings/SettingsSection.js +2 -2
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +4 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +76 -32
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareButton.spec.js +54 -7
- package/dist/client/sharing/ShareButton.spec.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +12 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +195 -53
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/server/core-routes-plugin.js +2 -2
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/poll-events.d.ts +12 -0
- package/dist/server/poll-events.d.ts.map +1 -0
- package/dist/server/poll-events.js +41 -0
- package/dist/server/poll-events.js.map +1 -0
- package/dist/server/poll.d.ts +4 -0
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +19 -12
- package/dist/server/poll.js.map +1 -1
- package/dist/templates/default/react-router.config.ts +1 -0
- package/package.json +1 -1
- package/src/templates/default/react-router.config.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShareButton.spec.js","sourceRoot":"","sources":["../../../src/client/sharing/ShareButton.spec.tsx"],"names":[],"mappings":";AAAA,gCAAgC;AAChC,OAAc,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9C,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;AAErE,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;QACrB,IAAI,EAAE;YACJ,UAAU,EAAE,mBAAmB;YAC/B,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,SAAS;YACrB,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,EAAE;SACX;QACD,OAAO,EAAE,aAAa;KACvB,CAAC;IACF,iBAAiB,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC;QACpC,MAAM,EAAE,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW;KAC9D,CAAC;CACH,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAiC,EAAE,EAAE,CAAC,CACxD,wBAAM,QAAQ,GAAO,CACtB;IACD,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAiC,EAAE,EAAE,CAAC,CAC/D,4BAAG,QAAQ,GAAI,CAChB;IACD,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAiC,EAAE,EAAE,CAAC,CAC/D,wBAAM,QAAQ,GAAO,CACtB;CACF,CAAC,CAAC,CAAC;AAEJ,SAAS,aAAa,CAAC,KAAuB,EAAE,KAAa;IAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,wBAAwB,CAC5C,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,EAC5B,OAAO,CACR,EAAE,GAAG,CAAC;IACP,GAAG,CAAC,GAAG,EAAE;QACP,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3B,KAAK,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3D,KAAK,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,SAAyB,CAAC;IAC9B,IAAI,IAAU,CAAC;IACf,IAAI,WAAwB,CAAC;IAE7B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,UAAU,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;QAChD,EAAE,CAAC,UAAU,CACX,OAAO,EACP,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CACf,QAAQ,CAAC,IAAI,CAAC;YACZ,OAAO,EAAE,EAAE;SACZ,CAAC,CACH,CACF,CAAC;QACF,WAAW,CAAC,SAAS,EAAE,CAAC;QACxB,WAAW,CAAC,SAAS,EAAE,CAAC;QACxB,aAAa,CAAC,SAAS,EAAE,CAAC;QAC1B,WAAW,GAAG,IAAI,WAAW,CAAC;YAC5B,cAAc,EAAE;gBACd,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;gBACzB,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;aAC5B;SACF,CAAC,CAAC;QACH,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1B,WAAW,CAAC,KAAK,EAAE,CAAC;QACpB,SAAS,CAAC,MAAM,EAAE,CAAC;QACnB,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;YACnB,IAAI,CAAC,MAAM,CACT,KAAC,mBAAmB,IAAC,MAAM,EAAE,WAAW,YACtC,KAAC,WAAW,IACV,YAAY,EAAC,UAAU,EACvB,UAAU,EAAC,OAAO,EAClB,aAAa,EAAC,cAAc,EAC5B,QAAQ,EAAC,6CAA6C,GACtD,GACkB,CACvB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,SAAS,CAAC,aAAa,CACnC,0CAA0C,CACvB,CAAC;QACtB,aAAa,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC;QAE7C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAChE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,KAAK,MAAM,CAC1C,CAAC;QACF,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAEpD,GAAG,CAAC,GAAG,EAAE;YACP,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,WAAW,CAAC,CAAC,oBAAoB,CACtC,MAAM,CAAC,gBAAgB,CAAC;YACtB,YAAY,EAAE,UAAU;YACxB,UAAU,EAAE,OAAO;YACnB,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,sBAAsB;YACnC,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,6CAA6C;SAC3D,CAAC,EACF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CACnB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @vitest-environment happy-dom\nimport React, { act } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { ShareButton } from \"./ShareButton.js\";\n\nconst shareMutate = vi.hoisted(() => vi.fn());\nconst otherMutate = vi.hoisted(() => vi.fn());\nconst refetchShares = vi.hoisted(() => vi.fn(async () => undefined));\n\nvi.mock(\"../use-action.js\", () => ({\n useActionQuery: () => ({\n data: {\n ownerEmail: \"owner@example.com\",\n orgId: null,\n visibility: \"private\",\n role: \"owner\",\n shares: [],\n },\n refetch: refetchShares,\n }),\n useActionMutation: (name: string) => ({\n mutate: name === \"share-resource\" ? shareMutate : otherMutate,\n }),\n}));\n\nvi.mock(\"../components/ui/popover.js\", () => ({\n Popover: ({ children }: { children: React.ReactNode }) => (\n <div>{children}</div>\n ),\n PopoverTrigger: ({ children }: { children: React.ReactNode }) => (\n <>{children}</>\n ),\n PopoverContent: ({ children }: { children: React.ReactNode }) => (\n <div>{children}</div>\n ),\n}));\n\nfunction setInputValue(input: HTMLInputElement, value: string) {\n const setter = Object.getOwnPropertyDescriptor(\n Object.getPrototypeOf(input),\n \"value\",\n )?.set;\n act(() => {\n setter?.call(input, value);\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n });\n}\n\ndescribe(\"ShareButton\", () => {\n let container: HTMLDivElement;\n let root: Root;\n let queryClient: QueryClient;\n\n beforeEach(() => {\n vi.stubGlobal(\"IS_REACT_ACT_ENVIRONMENT\", true);\n vi.stubGlobal(\n \"fetch\",\n vi.fn(async () =>\n Response.json({\n members: [],\n }),\n ),\n );\n shareMutate.mockReset();\n otherMutate.mockReset();\n refetchShares.mockClear();\n queryClient = new QueryClient({\n defaultOptions: {\n queries: { retry: false },\n mutations: { retry: false },\n },\n });\n container = document.createElement(\"div\");\n document.body.appendChild(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n queryClient.clear();\n container.remove();\n vi.unstubAllGlobals();\n });\n\n it(\"submits a typed email invite when Done is clicked\", async () => {\n await act(async () => {\n root.render(\n <QueryClientProvider client={queryClient}>\n <ShareButton\n resourceType=\"document\"\n resourceId=\"doc-1\"\n resourceTitle=\"Launch notes\"\n shareUrl=\"https://content.agent-native.com/page/doc-1\"\n />\n </QueryClientProvider>,\n );\n });\n\n const input = container.querySelector(\n 'input[placeholder=\"Add people by email\"]',\n ) as HTMLInputElement;\n setInputValue(input, \"teammate@example.com\");\n\n const done = Array.from(container.querySelectorAll(\"button\")).find(\n (button) => button.textContent === \"Done\",\n );\n if (!done) throw new Error(\"Done button not found\");\n\n act(() => {\n done.click();\n });\n\n expect(shareMutate).toHaveBeenCalledWith(\n expect.objectContaining({\n resourceType: \"document\",\n resourceId: \"doc-1\",\n principalType: \"user\",\n principalId: \"teammate@example.com\",\n role: \"viewer\",\n notify: true,\n resourceUrl: \"https://content.agent-native.com/page/doc-1\",\n }),\n expect.any(Object),\n );\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"ShareButton.spec.js","sourceRoot":"","sources":["../../../src/client/sharing/ShareButton.spec.tsx"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment happy-dom\nimport React, { act } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { createRoot, type Root } from \"react-dom/client\";\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { ShareButton } from \"./ShareButton.js\";\n\nconst shareMutate = vi.hoisted(() => vi.fn());\nconst otherMutate = vi.hoisted(() => vi.fn());\nconst refetchShares = vi.hoisted(() => vi.fn(async () => undefined));\nconst sharesData = vi.hoisted(() => ({\n current: {\n ownerEmail: \"owner@example.com\",\n orgId: null,\n visibility: \"private\",\n role: \"owner\",\n shares: [],\n },\n}));\n\nvi.mock(\"../use-action.js\", () => ({\n useActionQuery: () => ({\n data: sharesData.current,\n refetch: refetchShares,\n }),\n useActionMutation: (name: string) => ({\n mutate: name === \"share-resource\" ? shareMutate : otherMutate,\n }),\n}));\n\nvi.mock(\"../components/ui/popover.js\", () => ({\n Popover: ({ children }: { children: React.ReactNode }) => (\n <div>{children}</div>\n ),\n PopoverTrigger: ({ children }: { children: React.ReactNode }) => (\n <>{children}</>\n ),\n PopoverContent: ({ children }: { children: React.ReactNode }) => (\n <div>{children}</div>\n ),\n}));\n\nfunction setInputValue(input: HTMLInputElement, value: string) {\n const setter = Object.getOwnPropertyDescriptor(\n Object.getPrototypeOf(input),\n \"value\",\n )?.set;\n act(() => {\n setter?.call(input, value);\n input.dispatchEvent(new Event(\"input\", { bubbles: true }));\n input.dispatchEvent(new Event(\"change\", { bubbles: true }));\n });\n}\n\ndescribe(\"ShareButton\", () => {\n let container: HTMLDivElement;\n let root: Root;\n let queryClient: QueryClient;\n\n beforeEach(() => {\n vi.stubGlobal(\"IS_REACT_ACT_ENVIRONMENT\", true);\n vi.stubGlobal(\n \"fetch\",\n vi.fn(async () =>\n Response.json({\n members: [],\n }),\n ),\n );\n shareMutate.mockReset();\n otherMutate.mockReset();\n refetchShares.mockClear();\n sharesData.current = {\n ownerEmail: \"owner@example.com\",\n orgId: null,\n visibility: \"private\",\n role: \"owner\",\n shares: [],\n };\n queryClient = new QueryClient({\n defaultOptions: {\n queries: { retry: false },\n mutations: { retry: false },\n },\n });\n container = document.createElement(\"div\");\n document.body.appendChild(container);\n root = createRoot(container);\n });\n\n afterEach(() => {\n act(() => root.unmount());\n queryClient.clear();\n container.remove();\n vi.unstubAllGlobals();\n });\n\n it(\"submits a typed email invite when Done is clicked\", async () => {\n await act(async () => {\n root.render(\n <QueryClientProvider client={queryClient}>\n <ShareButton\n resourceType=\"document\"\n resourceId=\"doc-1\"\n resourceTitle=\"Launch notes\"\n shareUrl=\"https://content.agent-native.com/page/doc-1\"\n />\n </QueryClientProvider>,\n );\n });\n\n const input = container.querySelector(\n 'input[placeholder=\"Add people by email\"]',\n ) as HTMLInputElement;\n setInputValue(input, \"teammate@example.com\");\n\n const done = Array.from(container.querySelectorAll(\"button\")).find(\n (button) => button.textContent === \"Done\",\n );\n if (!done) throw new Error(\"Done button not found\");\n\n act(() => {\n done.click();\n });\n\n expect(shareMutate).toHaveBeenCalledWith(\n expect.objectContaining({\n resourceType: \"document\",\n resourceId: \"doc-1\",\n principalType: \"user\",\n principalId: \"teammate@example.com\",\n role: \"viewer\",\n notify: true,\n resourceUrl: \"https://content.agent-native.com/page/doc-1\",\n }),\n expect.any(Object),\n );\n });\n\n it(\"requires public visibility before copying a public-only link\", async () => {\n const writeText = vi.fn(async () => undefined);\n Object.defineProperty(navigator, \"clipboard\", {\n value: { writeText },\n configurable: true,\n });\n\n await act(async () => {\n root.render(\n <QueryClientProvider client={queryClient}>\n <ShareButton\n resourceType=\"deck\"\n resourceId=\"deck-1\"\n resourceTitle=\"Launch deck\"\n shareUrl=\"https://slides.agent-native.com/p/deck-1\"\n shareUrlRequiresPublic\n />\n </QueryClientProvider>,\n );\n });\n\n const makePublicAndCopy = Array.from(\n container.querySelectorAll(\"button\"),\n ).find((button) => button.textContent === \"Make public and copy\");\n if (!makePublicAndCopy) throw new Error(\"Make public button not found\");\n\n const linkInput = container.querySelector(\n 'input[value=\"Link available after general access is Public\"]',\n );\n expect(linkInput).toBeTruthy();\n\n await act(async () => {\n makePublicAndCopy.click();\n });\n\n expect(otherMutate).toHaveBeenCalledWith(\n expect.objectContaining({\n resourceType: \"deck\",\n resourceId: \"deck-1\",\n visibility: \"public\",\n }),\n expect.any(Object),\n );\n expect(writeText).not.toHaveBeenCalled();\n });\n\n it(\"shows the copy action for public public-only links\", async () => {\n sharesData.current = {\n ownerEmail: \"owner@example.com\",\n orgId: null,\n visibility: \"public\",\n role: \"owner\",\n shares: [],\n };\n\n await act(async () => {\n root.render(\n <QueryClientProvider client={queryClient}>\n <ShareButton\n resourceType=\"deck\"\n resourceId=\"deck-1\"\n shareUrl=\"https://slides.agent-native.com/p/deck-1\"\n shareUrlRequiresPublic\n />\n </QueryClientProvider>,\n );\n });\n\n expect(\n Array.from(container.querySelectorAll(\"button\")).some(\n (button) => button.textContent === \"Copy\",\n ),\n ).toBe(true);\n });\n});\n"]}
|
|
@@ -4,17 +4,23 @@ interface QueryClient {
|
|
|
4
4
|
}): void;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Hook that
|
|
8
|
-
* react-query caches when changes are detected.
|
|
7
|
+
* Hook that listens to /_agent-native/events for DB change events and
|
|
8
|
+
* invalidates react-query caches when changes are detected. Falls back to
|
|
9
|
+
* /_agent-native/poll so cross-process/serverless writes still show up.
|
|
9
10
|
*
|
|
10
11
|
* Works in all deployment environments (serverless, edge, long-lived server).
|
|
12
|
+
* SSE is the fast path; polling is the safety net.
|
|
11
13
|
*
|
|
12
14
|
* @param options.queryClient - The react-query QueryClient instance
|
|
13
15
|
* @param options.queryKeys - Array of query key prefixes to invalidate on change.
|
|
14
16
|
* Default: ["data"]
|
|
15
17
|
* @param options.pollUrl - Poll endpoint URL. Default: "/_agent-native/poll"
|
|
18
|
+
* @param options.sseUrl - SSE endpoint URL. Default: "/_agent-native/events".
|
|
19
|
+
* Pass false to disable SSE and use polling only.
|
|
16
20
|
* @param options.onEvent - Optional callback for each change event
|
|
17
21
|
* @param options.interval - Poll interval in ms. Default: 2000
|
|
22
|
+
* @param options.fallbackInterval - Poll interval while SSE is connected.
|
|
23
|
+
* Default: 15000
|
|
18
24
|
* @param options.pauseWhenHidden - Pause polling while the tab is hidden.
|
|
19
25
|
* Default: true
|
|
20
26
|
* @param options.ignoreSource - Skip events whose `requestSource` matches this
|
|
@@ -25,10 +31,12 @@ export declare function useDbSync(options?: {
|
|
|
25
31
|
queryClient?: QueryClient;
|
|
26
32
|
queryKeys?: string[];
|
|
27
33
|
pollUrl?: string;
|
|
34
|
+
sseUrl?: string | false;
|
|
28
35
|
/** @deprecated Use pollUrl instead */
|
|
29
36
|
eventsUrl?: string;
|
|
30
37
|
onEvent?: (data: any) => void;
|
|
31
38
|
interval?: number;
|
|
39
|
+
fallbackInterval?: number;
|
|
32
40
|
pauseWhenHidden?: boolean;
|
|
33
41
|
ignoreSource?: string;
|
|
34
42
|
}): void;
|
|
@@ -55,7 +63,9 @@ export declare const useFileWatcher: typeof useDbSync;
|
|
|
55
63
|
*/
|
|
56
64
|
export declare function useScreenRefreshKey(options?: {
|
|
57
65
|
pollUrl?: string;
|
|
66
|
+
sseUrl?: string | false;
|
|
58
67
|
interval?: number;
|
|
68
|
+
fallbackInterval?: number;
|
|
59
69
|
pauseWhenHidden?: boolean;
|
|
60
70
|
}): number;
|
|
61
71
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-db-sync.d.ts","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"AAGA,UAAU,WAAW;IACnB,iBAAiB,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;CACzD;
|
|
1
|
+
{"version":3,"file":"use-db-sync.d.ts","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"AAGA,UAAU,WAAW;IACnB,iBAAiB,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;CACzD;AA6ED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CACvB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,IAAI,CA2MN;AAED,wCAAwC;AACxC,eAAO,MAAM,cAAc,kBAAY,CAAC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,GACL,MAAM,CAsJR"}
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { agentNativePath } from "./api-path.js";
|
|
3
3
|
const POLL_ABORT_MIN_MS = 10_000;
|
|
4
|
+
const SSE_FALLBACK_INTERVAL_MS = 15_000;
|
|
4
5
|
function getPollAbortMs(interval) {
|
|
5
6
|
return Math.max(POLL_ABORT_MIN_MS, interval * 4);
|
|
6
7
|
}
|
|
7
8
|
function isDocumentHidden() {
|
|
8
9
|
return (typeof document !== "undefined" && document.visibilityState === "hidden");
|
|
9
10
|
}
|
|
11
|
+
function resolveSseUrl(sseUrl) {
|
|
12
|
+
if (sseUrl === false)
|
|
13
|
+
return false;
|
|
14
|
+
return agentNativePath(sseUrl ?? "/_agent-native/events");
|
|
15
|
+
}
|
|
16
|
+
function normalizeEventPayload(payload) {
|
|
17
|
+
if (!payload || typeof payload !== "object")
|
|
18
|
+
return [];
|
|
19
|
+
const record = payload;
|
|
20
|
+
if (record.type === "batch" && Array.isArray(record.events)) {
|
|
21
|
+
return record.events.filter((event) => !!event && typeof event === "object");
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(record.events)) {
|
|
24
|
+
return record.events.filter((event) => !!event && typeof event === "object");
|
|
25
|
+
}
|
|
26
|
+
return [payload];
|
|
27
|
+
}
|
|
28
|
+
function eventVersion(event) {
|
|
29
|
+
return typeof event.version === "number" ? event.version : 0;
|
|
30
|
+
}
|
|
10
31
|
async function fetchPollJson(pollUrl, since, interval) {
|
|
11
32
|
const controller = typeof AbortController === "undefined" ? null : new AbortController();
|
|
12
33
|
const timeout = controller
|
|
@@ -24,17 +45,23 @@ async function fetchPollJson(pollUrl, since, interval) {
|
|
|
24
45
|
}
|
|
25
46
|
}
|
|
26
47
|
/**
|
|
27
|
-
* Hook that
|
|
28
|
-
* react-query caches when changes are detected.
|
|
48
|
+
* Hook that listens to /_agent-native/events for DB change events and
|
|
49
|
+
* invalidates react-query caches when changes are detected. Falls back to
|
|
50
|
+
* /_agent-native/poll so cross-process/serverless writes still show up.
|
|
29
51
|
*
|
|
30
52
|
* Works in all deployment environments (serverless, edge, long-lived server).
|
|
53
|
+
* SSE is the fast path; polling is the safety net.
|
|
31
54
|
*
|
|
32
55
|
* @param options.queryClient - The react-query QueryClient instance
|
|
33
56
|
* @param options.queryKeys - Array of query key prefixes to invalidate on change.
|
|
34
57
|
* Default: ["data"]
|
|
35
58
|
* @param options.pollUrl - Poll endpoint URL. Default: "/_agent-native/poll"
|
|
59
|
+
* @param options.sseUrl - SSE endpoint URL. Default: "/_agent-native/events".
|
|
60
|
+
* Pass false to disable SSE and use polling only.
|
|
36
61
|
* @param options.onEvent - Optional callback for each change event
|
|
37
62
|
* @param options.interval - Poll interval in ms. Default: 2000
|
|
63
|
+
* @param options.fallbackInterval - Poll interval while SSE is connected.
|
|
64
|
+
* Default: 15000
|
|
38
65
|
* @param options.pauseWhenHidden - Pause polling while the tab is hidden.
|
|
39
66
|
* Default: true
|
|
40
67
|
* @param options.ignoreSource - Skip events whose `requestSource` matches this
|
|
@@ -42,7 +69,7 @@ async function fetchPollJson(pollUrl, since, interval) {
|
|
|
42
69
|
* picking up changes from other tabs, agents, and scripts.
|
|
43
70
|
*/
|
|
44
71
|
export function useDbSync(options = {}) {
|
|
45
|
-
const { queryClient, queryKeys = ["data"], pollUrl = agentNativePath(options.eventsUrl ?? "/_agent-native/poll"), interval = 2000, pauseWhenHidden = true, } = options;
|
|
72
|
+
const { queryClient, queryKeys = ["data"], pollUrl = agentNativePath(options.eventsUrl ?? "/_agent-native/poll"), sseUrl = resolveSseUrl(options.sseUrl), interval = 2000, fallbackInterval = Math.max(options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS, interval), pauseWhenHidden = true, } = options;
|
|
46
73
|
const onEventRef = useRef(options.onEvent);
|
|
47
74
|
onEventRef.current = options.onEvent;
|
|
48
75
|
const keysRef = useRef(queryKeys);
|
|
@@ -54,6 +81,8 @@ export function useDbSync(options = {}) {
|
|
|
54
81
|
let timer = null;
|
|
55
82
|
let stopped = false;
|
|
56
83
|
let inFlight = false;
|
|
84
|
+
let eventSource = null;
|
|
85
|
+
let sseConnected = false;
|
|
57
86
|
function schedulePoll() {
|
|
58
87
|
if (stopped)
|
|
59
88
|
return;
|
|
@@ -64,7 +93,86 @@ export function useDbSync(options = {}) {
|
|
|
64
93
|
timer = setTimeout(() => {
|
|
65
94
|
timer = null;
|
|
66
95
|
void poll();
|
|
67
|
-
}, interval);
|
|
96
|
+
}, sseConnected ? fallbackInterval : interval);
|
|
97
|
+
}
|
|
98
|
+
function invalidateForEvents(events) {
|
|
99
|
+
const ignore = ignoreSourceRef.current;
|
|
100
|
+
const relevant = ignore
|
|
101
|
+
? events.filter((e) => e.requestSource !== ignore)
|
|
102
|
+
: events;
|
|
103
|
+
if (relevant.length > 0 && queryClient) {
|
|
104
|
+
for (const key of keysRef.current) {
|
|
105
|
+
queryClient.invalidateQueries({ queryKey: [key] });
|
|
106
|
+
}
|
|
107
|
+
// Framework-level invalidation: always invalidate framework query
|
|
108
|
+
// keys on any non-own change event so that mutating actions
|
|
109
|
+
// (agent or HTTP) auto-refresh the UI — regardless of how the
|
|
110
|
+
// template configured queryKeys / onEvent.
|
|
111
|
+
queryClient.invalidateQueries({ queryKey: ["action"] });
|
|
112
|
+
queryClient.invalidateQueries({ queryKey: ["extension"] });
|
|
113
|
+
queryClient.invalidateQueries({ queryKey: ["extensions"] });
|
|
114
|
+
queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
|
|
115
|
+
queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
|
|
116
|
+
queryClient.invalidateQueries({ queryKey: ["slot-available"] });
|
|
117
|
+
queryClient.invalidateQueries({ queryKey: ["tool"] });
|
|
118
|
+
queryClient.invalidateQueries({ queryKey: ["tools"] });
|
|
119
|
+
queryClient.invalidateQueries({ queryKey: ["app-state"] });
|
|
120
|
+
queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
|
|
121
|
+
queryClient.invalidateQueries({ queryKey: ["show-questions"] });
|
|
122
|
+
queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
|
|
123
|
+
}
|
|
124
|
+
// Always forward all events to onEvent — templates can decide.
|
|
125
|
+
for (const evt of events) {
|
|
126
|
+
onEventRef.current?.(evt);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function applyEvents(events, version) {
|
|
130
|
+
const freshEvents = events.filter((event) => {
|
|
131
|
+
const version = eventVersion(event);
|
|
132
|
+
return version === 0 || version > versionRef;
|
|
133
|
+
});
|
|
134
|
+
if (freshEvents.length > 0) {
|
|
135
|
+
invalidateForEvents(freshEvents);
|
|
136
|
+
}
|
|
137
|
+
const maxEventVersion = freshEvents.reduce((max, event) => Math.max(max, eventVersion(event)), 0);
|
|
138
|
+
versionRef = Math.max(versionRef, version ?? 0, maxEventVersion);
|
|
139
|
+
}
|
|
140
|
+
function closeEvents() {
|
|
141
|
+
if (!eventSource)
|
|
142
|
+
return;
|
|
143
|
+
eventSource.close();
|
|
144
|
+
eventSource = null;
|
|
145
|
+
sseConnected = false;
|
|
146
|
+
}
|
|
147
|
+
function connectEvents() {
|
|
148
|
+
if (stopped ||
|
|
149
|
+
!sseUrl ||
|
|
150
|
+
eventSource ||
|
|
151
|
+
typeof EventSource === "undefined" ||
|
|
152
|
+
(pauseWhenHidden && isDocumentHidden())) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const source = new EventSource(sseUrl);
|
|
156
|
+
eventSource = source;
|
|
157
|
+
source.onopen = () => {
|
|
158
|
+
sseConnected = true;
|
|
159
|
+
schedulePoll();
|
|
160
|
+
};
|
|
161
|
+
source.onerror = () => {
|
|
162
|
+
sseConnected = false;
|
|
163
|
+
schedulePoll();
|
|
164
|
+
};
|
|
165
|
+
source.onmessage = (message) => {
|
|
166
|
+
try {
|
|
167
|
+
const payload = JSON.parse(message.data);
|
|
168
|
+
const events = normalizeEventPayload(payload);
|
|
169
|
+
const version = typeof payload?.version === "number" ? payload.version : undefined;
|
|
170
|
+
applyEvents(events, version);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Ignore malformed SSE frames; polling is the safety net.
|
|
174
|
+
}
|
|
175
|
+
};
|
|
68
176
|
}
|
|
69
177
|
async function poll() {
|
|
70
178
|
if (stopped || inFlight)
|
|
@@ -72,41 +180,7 @@ export function useDbSync(options = {}) {
|
|
|
72
180
|
inFlight = true;
|
|
73
181
|
try {
|
|
74
182
|
const data = await fetchPollJson(pollUrl, versionRef, interval);
|
|
75
|
-
|
|
76
|
-
if (events.length > 0 && queryClient) {
|
|
77
|
-
const ignore = ignoreSourceRef.current;
|
|
78
|
-
const relevant = ignore
|
|
79
|
-
? events.filter((e) => e.requestSource !== ignore)
|
|
80
|
-
: events;
|
|
81
|
-
if (relevant.length > 0) {
|
|
82
|
-
for (const key of keysRef.current) {
|
|
83
|
-
queryClient.invalidateQueries({ queryKey: [key] });
|
|
84
|
-
}
|
|
85
|
-
// Framework-level invalidation: always invalidate framework query
|
|
86
|
-
// keys on any non-own change event so that mutating actions
|
|
87
|
-
// (agent or HTTP) auto-refresh the UI — regardless of how the
|
|
88
|
-
// template configured queryKeys / onEvent.
|
|
89
|
-
queryClient.invalidateQueries({ queryKey: ["action"] });
|
|
90
|
-
queryClient.invalidateQueries({ queryKey: ["extension"] });
|
|
91
|
-
queryClient.invalidateQueries({ queryKey: ["extensions"] });
|
|
92
|
-
queryClient.invalidateQueries({ queryKey: ["extension-slots"] });
|
|
93
|
-
queryClient.invalidateQueries({ queryKey: ["slot-installs"] });
|
|
94
|
-
queryClient.invalidateQueries({ queryKey: ["slot-available"] });
|
|
95
|
-
queryClient.invalidateQueries({ queryKey: ["tool"] });
|
|
96
|
-
queryClient.invalidateQueries({ queryKey: ["tools"] });
|
|
97
|
-
queryClient.invalidateQueries({ queryKey: ["app-state"] });
|
|
98
|
-
queryClient.invalidateQueries({ queryKey: ["navigate-command"] });
|
|
99
|
-
queryClient.invalidateQueries({ queryKey: ["show-questions"] });
|
|
100
|
-
queryClient.invalidateQueries({ queryKey: ["__set_url__"] });
|
|
101
|
-
}
|
|
102
|
-
// Always forward all events to onEvent — templates can decide
|
|
103
|
-
for (const evt of events) {
|
|
104
|
-
onEventRef.current?.(evt);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Never decrease — protects against serverless instances with
|
|
108
|
-
// slightly different version counters.
|
|
109
|
-
versionRef = Math.max(versionRef, version);
|
|
183
|
+
applyEvents(data.events ?? [], data.version);
|
|
110
184
|
}
|
|
111
185
|
catch {
|
|
112
186
|
// Network error — will retry on next interval
|
|
@@ -124,31 +198,45 @@ export function useDbSync(options = {}) {
|
|
|
124
198
|
clearTimeout(timer);
|
|
125
199
|
timer = null;
|
|
126
200
|
}
|
|
201
|
+
connectEvents();
|
|
127
202
|
void poll();
|
|
128
203
|
}
|
|
129
204
|
function handleVisibilityChange() {
|
|
130
205
|
if (document.visibilityState === "visible") {
|
|
206
|
+
connectEvents();
|
|
131
207
|
pollNow();
|
|
132
208
|
}
|
|
133
|
-
else if (pauseWhenHidden
|
|
134
|
-
|
|
135
|
-
timer
|
|
209
|
+
else if (pauseWhenHidden) {
|
|
210
|
+
closeEvents();
|
|
211
|
+
if (timer) {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
timer = null;
|
|
214
|
+
}
|
|
136
215
|
}
|
|
137
216
|
}
|
|
138
217
|
// Initial poll immediately when visible. Hidden tabs catch up on focus.
|
|
139
218
|
if (!pauseWhenHidden || !isDocumentHidden()) {
|
|
219
|
+
connectEvents();
|
|
140
220
|
void poll();
|
|
141
221
|
}
|
|
142
222
|
window.addEventListener("focus", pollNow);
|
|
143
223
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
144
224
|
return () => {
|
|
145
225
|
stopped = true;
|
|
226
|
+
closeEvents();
|
|
146
227
|
if (timer)
|
|
147
228
|
clearTimeout(timer);
|
|
148
229
|
window.removeEventListener("focus", pollNow);
|
|
149
230
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
150
231
|
};
|
|
151
|
-
}, [
|
|
232
|
+
}, [
|
|
233
|
+
pollUrl,
|
|
234
|
+
sseUrl,
|
|
235
|
+
queryClient,
|
|
236
|
+
interval,
|
|
237
|
+
fallbackInterval,
|
|
238
|
+
pauseWhenHidden,
|
|
239
|
+
]);
|
|
152
240
|
}
|
|
153
241
|
/** @deprecated Use useDbSync instead */
|
|
154
242
|
export const useFileWatcher = useDbSync;
|
|
@@ -172,13 +260,15 @@ export const useFileWatcher = useDbSync;
|
|
|
172
260
|
* );
|
|
173
261
|
*/
|
|
174
262
|
export function useScreenRefreshKey(options = {}) {
|
|
175
|
-
const { pollUrl = agentNativePath(options.pollUrl ?? "/_agent-native/poll"), interval = 2000, pauseWhenHidden = true, } = options;
|
|
263
|
+
const { pollUrl = agentNativePath(options.pollUrl ?? "/_agent-native/poll"), sseUrl = resolveSseUrl(options.sseUrl), interval = 2000, fallbackInterval = Math.max(options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS, interval), pauseWhenHidden = true, } = options;
|
|
176
264
|
const [key, setKey] = useState(0);
|
|
177
265
|
useEffect(() => {
|
|
178
266
|
let versionRef = 0;
|
|
179
267
|
let timer = null;
|
|
180
268
|
let stopped = false;
|
|
181
269
|
let inFlight = false;
|
|
270
|
+
let eventSource = null;
|
|
271
|
+
let sseConnected = false;
|
|
182
272
|
function schedulePoll() {
|
|
183
273
|
if (stopped)
|
|
184
274
|
return;
|
|
@@ -189,7 +279,55 @@ export function useScreenRefreshKey(options = {}) {
|
|
|
189
279
|
timer = setTimeout(() => {
|
|
190
280
|
timer = null;
|
|
191
281
|
void poll();
|
|
192
|
-
}, interval);
|
|
282
|
+
}, sseConnected ? fallbackInterval : interval);
|
|
283
|
+
}
|
|
284
|
+
function applyEvents(events, version) {
|
|
285
|
+
const freshEvents = events.filter((event) => {
|
|
286
|
+
const version = eventVersion(event);
|
|
287
|
+
return version === 0 || version > versionRef;
|
|
288
|
+
});
|
|
289
|
+
if (freshEvents.some((e) => e.source === "screen-refresh")) {
|
|
290
|
+
setKey((k) => k + 1);
|
|
291
|
+
}
|
|
292
|
+
const maxEventVersion = freshEvents.reduce((max, event) => Math.max(max, eventVersion(event)), 0);
|
|
293
|
+
versionRef = Math.max(versionRef, version ?? 0, maxEventVersion);
|
|
294
|
+
}
|
|
295
|
+
function closeEvents() {
|
|
296
|
+
if (!eventSource)
|
|
297
|
+
return;
|
|
298
|
+
eventSource.close();
|
|
299
|
+
eventSource = null;
|
|
300
|
+
sseConnected = false;
|
|
301
|
+
}
|
|
302
|
+
function connectEvents() {
|
|
303
|
+
if (stopped ||
|
|
304
|
+
!sseUrl ||
|
|
305
|
+
eventSource ||
|
|
306
|
+
typeof EventSource === "undefined" ||
|
|
307
|
+
(pauseWhenHidden && isDocumentHidden())) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const source = new EventSource(sseUrl);
|
|
311
|
+
eventSource = source;
|
|
312
|
+
source.onopen = () => {
|
|
313
|
+
sseConnected = true;
|
|
314
|
+
schedulePoll();
|
|
315
|
+
};
|
|
316
|
+
source.onerror = () => {
|
|
317
|
+
sseConnected = false;
|
|
318
|
+
schedulePoll();
|
|
319
|
+
};
|
|
320
|
+
source.onmessage = (message) => {
|
|
321
|
+
try {
|
|
322
|
+
const payload = JSON.parse(message.data);
|
|
323
|
+
const events = normalizeEventPayload(payload);
|
|
324
|
+
const version = typeof payload?.version === "number" ? payload.version : undefined;
|
|
325
|
+
applyEvents(events, version);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Polling will catch missed screen-refresh events.
|
|
329
|
+
}
|
|
330
|
+
};
|
|
193
331
|
}
|
|
194
332
|
async function poll() {
|
|
195
333
|
if (stopped || inFlight)
|
|
@@ -197,10 +335,7 @@ export function useScreenRefreshKey(options = {}) {
|
|
|
197
335
|
inFlight = true;
|
|
198
336
|
try {
|
|
199
337
|
const data = await fetchPollJson(pollUrl, versionRef, interval);
|
|
200
|
-
|
|
201
|
-
setKey((k) => k + 1);
|
|
202
|
-
}
|
|
203
|
-
versionRef = Math.max(versionRef, data.version);
|
|
338
|
+
applyEvents(data.events ?? [], data.version);
|
|
204
339
|
}
|
|
205
340
|
catch {
|
|
206
341
|
// Network error — retry on next interval.
|
|
@@ -218,30 +353,37 @@ export function useScreenRefreshKey(options = {}) {
|
|
|
218
353
|
clearTimeout(timer);
|
|
219
354
|
timer = null;
|
|
220
355
|
}
|
|
356
|
+
connectEvents();
|
|
221
357
|
void poll();
|
|
222
358
|
}
|
|
223
359
|
function handleVisibilityChange() {
|
|
224
360
|
if (document.visibilityState === "visible") {
|
|
361
|
+
connectEvents();
|
|
225
362
|
pollNow();
|
|
226
363
|
}
|
|
227
|
-
else if (pauseWhenHidden
|
|
228
|
-
|
|
229
|
-
timer
|
|
364
|
+
else if (pauseWhenHidden) {
|
|
365
|
+
closeEvents();
|
|
366
|
+
if (timer) {
|
|
367
|
+
clearTimeout(timer);
|
|
368
|
+
timer = null;
|
|
369
|
+
}
|
|
230
370
|
}
|
|
231
371
|
}
|
|
232
372
|
if (!pauseWhenHidden || !isDocumentHidden()) {
|
|
373
|
+
connectEvents();
|
|
233
374
|
void poll();
|
|
234
375
|
}
|
|
235
376
|
window.addEventListener("focus", pollNow);
|
|
236
377
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
237
378
|
return () => {
|
|
238
379
|
stopped = true;
|
|
380
|
+
closeEvents();
|
|
239
381
|
if (timer)
|
|
240
382
|
clearTimeout(timer);
|
|
241
383
|
window.removeEventListener("focus", pollNow);
|
|
242
384
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
243
385
|
};
|
|
244
|
-
}, [pollUrl, interval, pauseWhenHidden]);
|
|
386
|
+
}, [pollUrl, sseUrl, interval, fallbackInterval, pauseWhenHidden]);
|
|
245
387
|
return key;
|
|
246
388
|
}
|
|
247
389
|
//# sourceMappingURL=use-db-sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-db-sync.js","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\ninterface QueryClient {\n invalidateQueries(opts?: { queryKey?: string[] }): void;\n}\n\nconst POLL_ABORT_MIN_MS = 10_000;\n\nfunction getPollAbortMs(interval: number): number {\n return Math.max(POLL_ABORT_MIN_MS, interval * 4);\n}\n\nfunction isDocumentHidden(): boolean {\n return (\n typeof document !== \"undefined\" && document.visibilityState === \"hidden\"\n );\n}\n\nasync function fetchPollJson<T>(\n pollUrl: string,\n since: number,\n interval: number,\n): Promise<T> {\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const timeout = controller\n ? setTimeout(() => controller.abort(), getPollAbortMs(interval))\n : null;\n\n try {\n const res = await fetch(\n `${pollUrl}?since=${since}`,\n controller ? { signal: controller.signal } : undefined,\n );\n if (!res.ok) throw new Error(\"HTTP \" + res.status);\n return res.json();\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\n/**\n * Hook that polls /_agent-native/poll for DB change events and invalidates\n * react-query caches when changes are detected.\n *\n * Works in all deployment environments (serverless, edge, long-lived server).\n *\n * @param options.queryClient - The react-query QueryClient instance\n * @param options.queryKeys - Array of query key prefixes to invalidate on change.\n * Default: [\"data\"]\n * @param options.pollUrl - Poll endpoint URL. Default: \"/_agent-native/poll\"\n * @param options.onEvent - Optional callback for each change event\n * @param options.interval - Poll interval in ms. Default: 2000\n * @param options.pauseWhenHidden - Pause polling while the tab is hidden.\n * Default: true\n * @param options.ignoreSource - Skip events whose `requestSource` matches this\n * value. Use a per-tab ID so the UI ignores its own writes while still\n * picking up changes from other tabs, agents, and scripts.\n */\nexport function useDbSync(\n options: {\n queryClient?: QueryClient;\n queryKeys?: string[];\n pollUrl?: string;\n /** @deprecated Use pollUrl instead */\n eventsUrl?: string;\n onEvent?: (data: any) => void;\n interval?: number;\n pauseWhenHidden?: boolean;\n ignoreSource?: string;\n } = {},\n): void {\n const {\n queryClient,\n queryKeys = [\"data\"],\n pollUrl = agentNativePath(options.eventsUrl ?? \"/_agent-native/poll\"),\n interval = 2000,\n pauseWhenHidden = true,\n } = options;\n\n const onEventRef = useRef(options.onEvent);\n onEventRef.current = options.onEvent;\n\n const keysRef = useRef(queryKeys);\n keysRef.current = queryKeys;\n\n const ignoreSourceRef = useRef(options.ignoreSource);\n ignoreSourceRef.current = options.ignoreSource;\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n let inFlight = false;\n\n function schedulePoll() {\n if (stopped) return;\n if (pauseWhenHidden && isDocumentHidden()) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n void poll();\n }, interval);\n }\n\n async function poll() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const data = await fetchPollJson<{\n version: number;\n events: Array<{\n source: string;\n type: string;\n key?: string;\n requestSource?: string;\n }>;\n }>(pollUrl, versionRef, interval);\n const { version, events } = data as {\n version: number;\n events: Array<{\n source: string;\n type: string;\n key?: string;\n requestSource?: string;\n }>;\n };\n\n if (events.length > 0 && queryClient) {\n const ignore = ignoreSourceRef.current;\n const relevant = ignore\n ? events.filter((e: any) => e.requestSource !== ignore)\n : events;\n\n if (relevant.length > 0) {\n for (const key of keysRef.current) {\n queryClient.invalidateQueries({ queryKey: [key] });\n }\n\n // Framework-level invalidation: always invalidate framework query\n // keys on any non-own change event so that mutating actions\n // (agent or HTTP) auto-refresh the UI — regardless of how the\n // template configured queryKeys / onEvent.\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension\"] });\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension-slots\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-available\"] });\n queryClient.invalidateQueries({ queryKey: [\"tool\"] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n queryClient.invalidateQueries({ queryKey: [\"app-state\"] });\n queryClient.invalidateQueries({ queryKey: [\"navigate-command\"] });\n queryClient.invalidateQueries({ queryKey: [\"show-questions\"] });\n queryClient.invalidateQueries({ queryKey: [\"__set_url__\"] });\n }\n\n // Always forward all events to onEvent — templates can decide\n for (const evt of events) {\n onEventRef.current?.(evt);\n }\n }\n\n // Never decrease — protects against serverless instances with\n // slightly different version counters.\n versionRef = Math.max(versionRef, version);\n } catch {\n // Network error — will retry on next interval\n } finally {\n inFlight = false;\n schedulePoll();\n }\n }\n\n function pollNow() {\n if (pauseWhenHidden && isDocumentHidden()) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n void poll();\n }\n\n function handleVisibilityChange() {\n if (document.visibilityState === \"visible\") {\n pollNow();\n } else if (pauseWhenHidden && timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n // Initial poll immediately when visible. Hidden tabs catch up on focus.\n if (!pauseWhenHidden || !isDocumentHidden()) {\n void poll();\n }\n window.addEventListener(\"focus\", pollNow);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n window.removeEventListener(\"focus\", pollNow);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [pollUrl, queryClient, interval, pauseWhenHidden]);\n}\n\n/** @deprecated Use useDbSync instead */\nexport const useFileWatcher = useDbSync;\n\n/**\n * Subscribe to `refresh-screen` events from the agent. Returns an integer\n * that increments every time the agent invokes the framework's `refresh-screen`\n * tool. Apply it as a React `key` on the main content wrapper (the part\n * OUTSIDE the agent chat sidebar) so that region remounts and re-fetches its\n * data while the chat, sidebar, and any other persistent chrome keep their\n * in-flight state.\n *\n * Usage in a template's root:\n *\n * const screenKey = useScreenRefreshKey();\n * return (\n * <AppLayout>\n * <div key={screenKey}>\n * <Outlet />\n * </div>\n * </AppLayout>\n * );\n */\nexport function useScreenRefreshKey(\n options: {\n pollUrl?: string;\n interval?: number;\n pauseWhenHidden?: boolean;\n } = {},\n): number {\n const {\n pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\"),\n interval = 2000,\n pauseWhenHidden = true,\n } = options;\n const [key, setKey] = useState(0);\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n let inFlight = false;\n\n function schedulePoll() {\n if (stopped) return;\n if (pauseWhenHidden && isDocumentHidden()) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n void poll();\n }, interval);\n }\n\n async function poll() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const data = await fetchPollJson<{\n version: number;\n events: Array<{ source: string }>;\n }>(pollUrl, versionRef, interval);\n if (data.events?.some((e) => e.source === \"screen-refresh\")) {\n setKey((k) => k + 1);\n }\n versionRef = Math.max(versionRef, data.version);\n } catch {\n // Network error — retry on next interval.\n } finally {\n inFlight = false;\n schedulePoll();\n }\n }\n\n function pollNow() {\n if (pauseWhenHidden && isDocumentHidden()) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n void poll();\n }\n\n function handleVisibilityChange() {\n if (document.visibilityState === \"visible\") {\n pollNow();\n } else if (pauseWhenHidden && timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n if (!pauseWhenHidden || !isDocumentHidden()) {\n void poll();\n }\n window.addEventListener(\"focus\", pollNow);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n stopped = true;\n if (timer) clearTimeout(timer);\n window.removeEventListener(\"focus\", pollNow);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [pollUrl, interval, pauseWhenHidden]);\n\n return key;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-db-sync.js","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { agentNativePath } from \"./api-path.js\";\n\ninterface QueryClient {\n invalidateQueries(opts?: { queryKey?: string[] }): void;\n}\n\nconst POLL_ABORT_MIN_MS = 10_000;\nconst SSE_FALLBACK_INTERVAL_MS = 15_000;\n\ntype SyncEvent = {\n version?: number;\n source?: string;\n type?: string;\n key?: string;\n requestSource?: string;\n [k: string]: unknown;\n};\n\ntype PollResponse = {\n version: number;\n events: SyncEvent[];\n};\n\nfunction getPollAbortMs(interval: number): number {\n return Math.max(POLL_ABORT_MIN_MS, interval * 4);\n}\n\nfunction isDocumentHidden(): boolean {\n return (\n typeof document !== \"undefined\" && document.visibilityState === \"hidden\"\n );\n}\n\nfunction resolveSseUrl(sseUrl: string | false | undefined): string | false {\n if (sseUrl === false) return false;\n return agentNativePath(sseUrl ?? \"/_agent-native/events\");\n}\n\nfunction normalizeEventPayload(payload: unknown): SyncEvent[] {\n if (!payload || typeof payload !== \"object\") return [];\n const record = payload as { type?: unknown; events?: unknown };\n if (record.type === \"batch\" && Array.isArray(record.events)) {\n return record.events.filter(\n (event): event is SyncEvent => !!event && typeof event === \"object\",\n );\n }\n if (Array.isArray(record.events)) {\n return record.events.filter(\n (event): event is SyncEvent => !!event && typeof event === \"object\",\n );\n }\n return [payload as SyncEvent];\n}\n\nfunction eventVersion(event: SyncEvent): number {\n return typeof event.version === \"number\" ? event.version : 0;\n}\n\nasync function fetchPollJson<T>(\n pollUrl: string,\n since: number,\n interval: number,\n): Promise<T> {\n const controller =\n typeof AbortController === \"undefined\" ? null : new AbortController();\n const timeout = controller\n ? setTimeout(() => controller.abort(), getPollAbortMs(interval))\n : null;\n\n try {\n const res = await fetch(\n `${pollUrl}?since=${since}`,\n controller ? { signal: controller.signal } : undefined,\n );\n if (!res.ok) throw new Error(\"HTTP \" + res.status);\n return res.json();\n } finally {\n if (timeout) clearTimeout(timeout);\n }\n}\n\n/**\n * Hook that listens to /_agent-native/events for DB change events and\n * invalidates react-query caches when changes are detected. Falls back to\n * /_agent-native/poll so cross-process/serverless writes still show up.\n *\n * Works in all deployment environments (serverless, edge, long-lived server).\n * SSE is the fast path; polling is the safety net.\n *\n * @param options.queryClient - The react-query QueryClient instance\n * @param options.queryKeys - Array of query key prefixes to invalidate on change.\n * Default: [\"data\"]\n * @param options.pollUrl - Poll endpoint URL. Default: \"/_agent-native/poll\"\n * @param options.sseUrl - SSE endpoint URL. Default: \"/_agent-native/events\".\n * Pass false to disable SSE and use polling only.\n * @param options.onEvent - Optional callback for each change event\n * @param options.interval - Poll interval in ms. Default: 2000\n * @param options.fallbackInterval - Poll interval while SSE is connected.\n * Default: 15000\n * @param options.pauseWhenHidden - Pause polling while the tab is hidden.\n * Default: true\n * @param options.ignoreSource - Skip events whose `requestSource` matches this\n * value. Use a per-tab ID so the UI ignores its own writes while still\n * picking up changes from other tabs, agents, and scripts.\n */\nexport function useDbSync(\n options: {\n queryClient?: QueryClient;\n queryKeys?: string[];\n pollUrl?: string;\n sseUrl?: string | false;\n /** @deprecated Use pollUrl instead */\n eventsUrl?: string;\n onEvent?: (data: any) => void;\n interval?: number;\n fallbackInterval?: number;\n pauseWhenHidden?: boolean;\n ignoreSource?: string;\n } = {},\n): void {\n const {\n queryClient,\n queryKeys = [\"data\"],\n pollUrl = agentNativePath(options.eventsUrl ?? \"/_agent-native/poll\"),\n sseUrl = resolveSseUrl(options.sseUrl),\n interval = 2000,\n fallbackInterval = Math.max(\n options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,\n interval,\n ),\n pauseWhenHidden = true,\n } = options;\n\n const onEventRef = useRef(options.onEvent);\n onEventRef.current = options.onEvent;\n\n const keysRef = useRef(queryKeys);\n keysRef.current = queryKeys;\n\n const ignoreSourceRef = useRef(options.ignoreSource);\n ignoreSourceRef.current = options.ignoreSource;\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n let inFlight = false;\n let eventSource: EventSource | null = null;\n let sseConnected = false;\n\n function schedulePoll() {\n if (stopped) return;\n if (pauseWhenHidden && isDocumentHidden()) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(\n () => {\n timer = null;\n void poll();\n },\n sseConnected ? fallbackInterval : interval,\n );\n }\n\n function invalidateForEvents(events: SyncEvent[]) {\n const ignore = ignoreSourceRef.current;\n const relevant = ignore\n ? events.filter((e) => e.requestSource !== ignore)\n : events;\n\n if (relevant.length > 0 && queryClient) {\n for (const key of keysRef.current) {\n queryClient.invalidateQueries({ queryKey: [key] });\n }\n\n // Framework-level invalidation: always invalidate framework query\n // keys on any non-own change event so that mutating actions\n // (agent or HTTP) auto-refresh the UI — regardless of how the\n // template configured queryKeys / onEvent.\n queryClient.invalidateQueries({ queryKey: [\"action\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension\"] });\n queryClient.invalidateQueries({ queryKey: [\"extensions\"] });\n queryClient.invalidateQueries({ queryKey: [\"extension-slots\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-installs\"] });\n queryClient.invalidateQueries({ queryKey: [\"slot-available\"] });\n queryClient.invalidateQueries({ queryKey: [\"tool\"] });\n queryClient.invalidateQueries({ queryKey: [\"tools\"] });\n queryClient.invalidateQueries({ queryKey: [\"app-state\"] });\n queryClient.invalidateQueries({ queryKey: [\"navigate-command\"] });\n queryClient.invalidateQueries({ queryKey: [\"show-questions\"] });\n queryClient.invalidateQueries({ queryKey: [\"__set_url__\"] });\n }\n\n // Always forward all events to onEvent — templates can decide.\n for (const evt of events) {\n onEventRef.current?.(evt);\n }\n }\n\n function applyEvents(events: SyncEvent[], version?: number) {\n const freshEvents = events.filter((event) => {\n const version = eventVersion(event);\n return version === 0 || version > versionRef;\n });\n\n if (freshEvents.length > 0) {\n invalidateForEvents(freshEvents);\n }\n\n const maxEventVersion = freshEvents.reduce(\n (max, event) => Math.max(max, eventVersion(event)),\n 0,\n );\n versionRef = Math.max(versionRef, version ?? 0, maxEventVersion);\n }\n\n function closeEvents() {\n if (!eventSource) return;\n eventSource.close();\n eventSource = null;\n sseConnected = false;\n }\n\n function connectEvents() {\n if (\n stopped ||\n !sseUrl ||\n eventSource ||\n typeof EventSource === \"undefined\" ||\n (pauseWhenHidden && isDocumentHidden())\n ) {\n return;\n }\n\n const source = new EventSource(sseUrl);\n eventSource = source;\n source.onopen = () => {\n sseConnected = true;\n schedulePoll();\n };\n source.onerror = () => {\n sseConnected = false;\n schedulePoll();\n };\n source.onmessage = (message) => {\n try {\n const payload = JSON.parse(message.data);\n const events = normalizeEventPayload(payload);\n const version =\n typeof payload?.version === \"number\" ? payload.version : undefined;\n applyEvents(events, version);\n } catch {\n // Ignore malformed SSE frames; polling is the safety net.\n }\n };\n }\n\n async function poll() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const data = await fetchPollJson<PollResponse>(\n pollUrl,\n versionRef,\n interval,\n );\n applyEvents(data.events ?? [], data.version);\n } catch {\n // Network error — will retry on next interval\n } finally {\n inFlight = false;\n schedulePoll();\n }\n }\n\n function pollNow() {\n if (pauseWhenHidden && isDocumentHidden()) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n connectEvents();\n void poll();\n }\n\n function handleVisibilityChange() {\n if (document.visibilityState === \"visible\") {\n connectEvents();\n pollNow();\n } else if (pauseWhenHidden) {\n closeEvents();\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n }\n\n // Initial poll immediately when visible. Hidden tabs catch up on focus.\n if (!pauseWhenHidden || !isDocumentHidden()) {\n connectEvents();\n void poll();\n }\n window.addEventListener(\"focus\", pollNow);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n stopped = true;\n closeEvents();\n if (timer) clearTimeout(timer);\n window.removeEventListener(\"focus\", pollNow);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [\n pollUrl,\n sseUrl,\n queryClient,\n interval,\n fallbackInterval,\n pauseWhenHidden,\n ]);\n}\n\n/** @deprecated Use useDbSync instead */\nexport const useFileWatcher = useDbSync;\n\n/**\n * Subscribe to `refresh-screen` events from the agent. Returns an integer\n * that increments every time the agent invokes the framework's `refresh-screen`\n * tool. Apply it as a React `key` on the main content wrapper (the part\n * OUTSIDE the agent chat sidebar) so that region remounts and re-fetches its\n * data while the chat, sidebar, and any other persistent chrome keep their\n * in-flight state.\n *\n * Usage in a template's root:\n *\n * const screenKey = useScreenRefreshKey();\n * return (\n * <AppLayout>\n * <div key={screenKey}>\n * <Outlet />\n * </div>\n * </AppLayout>\n * );\n */\nexport function useScreenRefreshKey(\n options: {\n pollUrl?: string;\n sseUrl?: string | false;\n interval?: number;\n fallbackInterval?: number;\n pauseWhenHidden?: boolean;\n } = {},\n): number {\n const {\n pollUrl = agentNativePath(options.pollUrl ?? \"/_agent-native/poll\"),\n sseUrl = resolveSseUrl(options.sseUrl),\n interval = 2000,\n fallbackInterval = Math.max(\n options.fallbackInterval ?? SSE_FALLBACK_INTERVAL_MS,\n interval,\n ),\n pauseWhenHidden = true,\n } = options;\n const [key, setKey] = useState(0);\n\n useEffect(() => {\n let versionRef = 0;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let stopped = false;\n let inFlight = false;\n let eventSource: EventSource | null = null;\n let sseConnected = false;\n\n function schedulePoll() {\n if (stopped) return;\n if (pauseWhenHidden && isDocumentHidden()) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(\n () => {\n timer = null;\n void poll();\n },\n sseConnected ? fallbackInterval : interval,\n );\n }\n\n function applyEvents(events: SyncEvent[], version?: number) {\n const freshEvents = events.filter((event) => {\n const version = eventVersion(event);\n return version === 0 || version > versionRef;\n });\n if (freshEvents.some((e) => e.source === \"screen-refresh\")) {\n setKey((k) => k + 1);\n }\n const maxEventVersion = freshEvents.reduce(\n (max, event) => Math.max(max, eventVersion(event)),\n 0,\n );\n versionRef = Math.max(versionRef, version ?? 0, maxEventVersion);\n }\n\n function closeEvents() {\n if (!eventSource) return;\n eventSource.close();\n eventSource = null;\n sseConnected = false;\n }\n\n function connectEvents() {\n if (\n stopped ||\n !sseUrl ||\n eventSource ||\n typeof EventSource === \"undefined\" ||\n (pauseWhenHidden && isDocumentHidden())\n ) {\n return;\n }\n\n const source = new EventSource(sseUrl);\n eventSource = source;\n source.onopen = () => {\n sseConnected = true;\n schedulePoll();\n };\n source.onerror = () => {\n sseConnected = false;\n schedulePoll();\n };\n source.onmessage = (message) => {\n try {\n const payload = JSON.parse(message.data);\n const events = normalizeEventPayload(payload);\n const version =\n typeof payload?.version === \"number\" ? payload.version : undefined;\n applyEvents(events, version);\n } catch {\n // Polling will catch missed screen-refresh events.\n }\n };\n }\n\n async function poll() {\n if (stopped || inFlight) return;\n inFlight = true;\n try {\n const data = await fetchPollJson<PollResponse>(\n pollUrl,\n versionRef,\n interval,\n );\n applyEvents(data.events ?? [], data.version);\n } catch {\n // Network error — retry on next interval.\n } finally {\n inFlight = false;\n schedulePoll();\n }\n }\n\n function pollNow() {\n if (pauseWhenHidden && isDocumentHidden()) {\n return;\n }\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n connectEvents();\n void poll();\n }\n\n function handleVisibilityChange() {\n if (document.visibilityState === \"visible\") {\n connectEvents();\n pollNow();\n } else if (pauseWhenHidden) {\n closeEvents();\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n }\n\n if (!pauseWhenHidden || !isDocumentHidden()) {\n connectEvents();\n void poll();\n }\n window.addEventListener(\"focus\", pollNow);\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n stopped = true;\n closeEvents();\n if (timer) clearTimeout(timer);\n window.removeEventListener(\"focus\", pollNow);\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [pollUrl, sseUrl, interval, fallbackInterval, pauseWhenHidden]);\n\n return key;\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js
|
|
|
3
3
|
import { defineEventHandler, setResponseStatus, setResponseHeader, getMethod, getHeader, } from "h3";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createPollHandler } from "./poll.js";
|
|
6
|
-
import {
|
|
6
|
+
import { createPollEventsHandler } from "./poll-events.js";
|
|
7
7
|
import { upsertEnvFile } from "./create-server.js";
|
|
8
8
|
import { readBody } from "./h3-helpers.js";
|
|
9
9
|
import { BUILDER_CONNECT_PARAM, BUILDER_ENV_KEYS, appendBuilderConnectToken, buildBuilderCliAuthUrl, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, getBuilderBrowserStatusForEvent, resolveBuilderBranchProjectId, resolveSafePreviewUrl, runBuilderAgent, verifyBuilderConnectToken, } from "./builder-browser.js";
|
|
@@ -319,7 +319,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
319
319
|
// SSE
|
|
320
320
|
if (!options.disableSSE) {
|
|
321
321
|
const sseRoute = options.sseRoute ?? `${P}/events`;
|
|
322
|
-
getH3App(nitroApp).use(sseRoute,
|
|
322
|
+
getH3App(nitroApp).use(sseRoute, createPollEventsHandler());
|
|
323
323
|
}
|
|
324
324
|
// Ping
|
|
325
325
|
if (!options.disablePing) {
|