diff --git a/examples/flush-notes/src/app/WakuProvider.tsx b/examples/flush-notes/src/app/WakuProvider.tsx new file mode 100644 index 0000000..e4f0ce3 --- /dev/null +++ b/examples/flush-notes/src/app/WakuProvider.tsx @@ -0,0 +1,51 @@ +"use client"; + +import React from "react"; +import { waku, Waku, WakuEvents } from "@/services/waku"; + +type WakuContextProps = { + status: string; +}; + +const WakuContext = React.createContext({ + status: "", +}); + +type WakuProviderProps = { + children: React.ReactNode; +}; + +export const useWaku = () => { + const { status } = React.useContext(WakuContext); + + return { status }; +}; + +export const WakuProvider = (props: WakuProviderProps) => { + const wakuRef = React.useRef(); + const [status, setStatus] = React.useState(""); + + React.useEffect(() => { + if (wakuRef.current) { + return; + } + + const statusListener = (event: CustomEvent) => { + setStatus(event.detail); + }; + waku.addEventListener(WakuEvents.Status, statusListener); + + waku.init().then(() => { + wakuRef.current = waku; + }); + return () => { + waku.removeEventListener(WakuEvents.Status, statusListener); + }; + }, [wakuRef, setStatus]); + + return ( + + {props.children} + + ); +}; diff --git a/examples/flush-notes/src/app/create/page.tsx b/examples/flush-notes/src/app/create/page.tsx new file mode 100644 index 0000000..91f66b8 --- /dev/null +++ b/examples/flush-notes/src/app/create/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { useWaku } from "@/app/WakuProvider"; + +export default function Create() { + const { status } = useWaku(); + return ( +
+

{status}

+
+ ); +} diff --git a/examples/flush-notes/src/app/edit/page.tsx b/examples/flush-notes/src/app/edit/page.tsx new file mode 100644 index 0000000..9bf1250 --- /dev/null +++ b/examples/flush-notes/src/app/edit/page.tsx @@ -0,0 +1,48 @@ +"use client"; + +import React from "react"; +import Markdown from "react-markdown"; +import { useRouter } from "next/navigation"; +import { useNoteHash } from "@/hooks/useNoteHash"; + +const t = ` +### Problem +Cannot read data returned from REST API in browser if request is done with CORS violation. + +# Impact +Prevents browsers from querying REST API. + +### To reproduce +0. Run nwaku REST API +1. Copy https://github.com/waku-org/waku-frontend/tree/weboko/follow-up +5. Wait for 10 seconds and see in console following error: + +It happens even though the request succeds. + + +### Expected behavior +Error should not happen. + +### Additional context +What happens is that browser implements Fetch API in the manner that when request is made to resource with CORS violation then even if it would succeed - client won't be able to read response data. +Spec of the Fetch API - https://fetch.spec.whatwg.org/#concept-filtered-response-opaque + + +### The fix +Considering we expect REST API to be run only on localhost we should add following HTTP header to allow web apps run on different port to be able to talk to the API. +`; + +const View = () => { + const router = useRouter(); + const noteHash = useNoteHash(); + + React.useEffect(() => { + if (!noteHash) { + router.replace("/404"); + } + }, [noteHash]); + + return {t}; +}; + +export default View; diff --git a/examples/flush-notes/src/app/layout.tsx b/examples/flush-notes/src/app/layout.tsx index a3591a3..93d9a45 100644 --- a/examples/flush-notes/src/app/layout.tsx +++ b/examples/flush-notes/src/app/layout.tsx @@ -1,14 +1,12 @@ -import type { Metadata } from "next"; +"use client"; + +import React from "react"; import { Inter } from "next/font/google"; +import { WakuProvider } from "@/app/WakuProvider"; import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); -export const metadata: Metadata = { - title: "Flush notes", - description: "An app to share notes", -}; - export default function RootLayout({ children, }: { @@ -16,7 +14,9 @@ export default function RootLayout({ }) { return ( - {children} + + {children} + ); } diff --git a/examples/flush-notes/src/app/page.tsx b/examples/flush-notes/src/app/page.tsx index a642d91..e6d12ef 100644 --- a/examples/flush-notes/src/app/page.tsx +++ b/examples/flush-notes/src/app/page.tsx @@ -1,32 +1,22 @@ -import Markdown from "react-markdown"; +"use client"; -const t = ` -### Problem -Cannot read data returned from REST API in browser if request is done with CORS violation. - -# Impact -Prevents browsers from querying REST API. - -### To reproduce -0. Run nwaku REST API -1. Copy https://github.com/waku-org/waku-frontend/tree/weboko/follow-up -5. Wait for 10 seconds and see in console following error: - -It happens even though the request succeds. - - -### Expected behavior -Error should not happen. - -### Additional context -What happens is that browser implements Fetch API in the manner that when request is made to resource with CORS violation then even if it would succeed - client won't be able to read response data. -Spec of the Fetch API - https://fetch.spec.whatwg.org/#concept-filtered-response-opaque - - -### The fix -Considering we expect REST API to be run only on localhost we should add following HTTP header to allow web apps run on different port to be able to talk to the API. -`; +import { useWaku } from "@/app/WakuProvider"; +import { useRouter } from "next/navigation"; export default function Home() { - return {t}; + const { status } = useWaku(); + const router = useRouter(); + return ( +
+

{status}

+ + +
+ ); } diff --git a/examples/flush-notes/src/app/view/page.tsx b/examples/flush-notes/src/app/view/page.tsx index 405f5c9..9bf1250 100644 --- a/examples/flush-notes/src/app/view/page.tsx +++ b/examples/flush-notes/src/app/view/page.tsx @@ -3,7 +3,7 @@ import React from "react"; import Markdown from "react-markdown"; import { useRouter } from "next/navigation"; -import { useViewHash } from "@/hooks/useViewHash"; +import { useNoteHash } from "@/hooks/useNoteHash"; const t = ` ### Problem @@ -34,7 +34,7 @@ Considering we expect REST API to be run only on localhost we should add followi const View = () => { const router = useRouter(); - const noteHash = useViewHash(); + const noteHash = useNoteHash(); React.useEffect(() => { if (!noteHash) { diff --git a/examples/flush-notes/src/hooks/useViewHash.ts b/examples/flush-notes/src/hooks/useNoteHash.ts similarity index 79% rename from examples/flush-notes/src/hooks/useViewHash.ts rename to examples/flush-notes/src/hooks/useNoteHash.ts index 08f7be5..cbdca40 100644 --- a/examples/flush-notes/src/hooks/useViewHash.ts +++ b/examples/flush-notes/src/hooks/useNoteHash.ts @@ -1,6 +1,6 @@ import { usePathname } from "next/navigation"; -export const useViewHash = (): undefined | string => { +export const useNoteHash = (): undefined | string => { const pathname = usePathname(); const segments = pathname.split("/"); const viewIndex = segments.indexOf("view"); diff --git a/examples/flush-notes/src/services/waku.ts b/examples/flush-notes/src/services/waku.ts index 3b813be..8a87141 100644 --- a/examples/flush-notes/src/services/waku.ts +++ b/examples/flush-notes/src/services/waku.ts @@ -1,18 +1,59 @@ -import { LightNode, createLightNode } from "@waku/sdk"; +"use client"; -type WakuOptions = { - node: LightNode; -}; +import { LightNode, createLightNode, waitForRemotePeer } from "@waku/sdk"; + +type EventListener = (event: CustomEvent) => void; + +export enum WakuEvents { + Status = "status", +} export class Waku { private node: undefined | LightNode; - private constructor(options: WakuOptions) { - this.node = options.node; + private emitter = new EventTarget(); + private initialized: boolean = false; + private initializing: boolean = false; + + constructor() {} + + public async init(): Promise { + if (this.initialized || this.initializing) { + return; + } + + this.initializing = true; + console.log("Waku"); + try { + this.emitStatusEvent("Initializing..."); + const node = await createLightNode({ defaultBootstrap: true }); + await node.start(); + this.emitStatusEvent("Waiting for peers..."); + await waitForRemotePeer(node); + this.node = node; + this.initialized = true; + this.emitStatusEvent("Connected"); + } catch (error) { + console.error("Failed to initialize Waku node:", error); + this.emitStatusEvent("Failed to initialize(see logs)"); + } + this.initializing = false; } - public static async init(): Promise { - const node = await createLightNode({ defaultBootstrap: true }); - return new Waku({ node }); + public addEventListener(event: WakuEvents, fn: EventListener) { + return this.emitter.addEventListener(event, fn as any); + } + + public removeEventListener(event: WakuEvents, fn: EventListener) { + return this.emitter.removeEventListener(event, fn as any); + } + + private emitStatusEvent(payload: string) { + console.log(payload); + this.emitter.dispatchEvent( + new CustomEvent(WakuEvents.Status, { detail: payload }) + ); } } + +export const waku = new Waku();