Repository: mfrachet/use-socketio Branch: master Commit: 9c3d1d11984a Files: 55 Total size: 38.3 KB Directory structure: gitextract_ge2hlk0j/ ├── .github/ │ └── workflows/ │ └── test_pr.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── cypress/ │ ├── fixtures/ │ │ └── example.json │ ├── integration/ │ │ ├── socketio.spec.js │ │ ├── sse.spec.js │ │ └── websocket.spec.js │ ├── plugins/ │ │ └── index.js │ └── support/ │ ├── commands.js │ └── index.js ├── cypress.json ├── example/ │ ├── App.js │ ├── index.html │ ├── index.js │ ├── server-helpers.js │ ├── server.js │ ├── socketio/ │ │ ├── SocketIo.js │ │ ├── constants.js │ │ └── socketio-server.js │ ├── sse/ │ │ ├── SSE.js │ │ ├── constants.js │ │ └── sse-server.js │ └── websocket/ │ ├── Websocket.js │ ├── constants.js │ └── websocket-server.js ├── lerna.json ├── package.json └── packages/ ├── use-server-sent-events/ │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── context.ts │ │ ├── hooks.ts │ │ ├── index.tsx │ │ └── provider.tsx │ ├── tsconfig.json │ └── tslint.json ├── use-socketio/ │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── context.ts │ │ ├── hooks.ts │ │ ├── index.tsx │ │ └── provider.tsx │ ├── tsconfig.json │ └── tslint.json └── use-websockets/ ├── .npmignore ├── README.md ├── package.json ├── src/ │ ├── context.ts │ ├── hooks.ts │ ├── index.tsx │ └── provider.tsx ├── tsconfig.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/test_pr.yml ================================================ # This workflow will run when a pull request event is triggered (push, open, whatever happens) name: Node.js test on: pull_request: branches: ['main'] workflow_dispatch: # just in case when you need to trigger the thing manually without pull requests jobs: cypress-run: runs-on: ubuntu-16.04 continue-on-error: true steps: - name: Install libgconf-2-4 run: sudo apt-get install libgconf-2-4 # Don't worry about passwords, sudo won't prompt for any, cuz there isn't a password here. - uses: actions/checkout@v2 - name: Setup Node.js v10 🎁 uses: actions/setup-node@v1 with: node-version: 10.x - name: Install dependencies 📦 run: npm ci - name: Setup cache uses: actions/cache@v2 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Bootstrap 🥾 run: npm run bootstrap - name: restore lerna uses: actions/cache@v2 with: path: | node_modules */*/node_modules key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} - name: Lint 🔍 run: npm run check:lint - name: Build the hooks 🛠 run: npm run build - name: Test the hooks ✔ uses: cypress-io/github-action@v2 with: start: npm start, npm run start:test-server - name: Upload the videos uses: actions/upload-artifact@v2 with: name: cypress-videos path: cypress/videos/* ================================================ FILE: .gitignore ================================================ node_modules .cache dist cypress/videos lib ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2004-Today Marvin Frachet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Build Status](https://travis-ci.org/mfrachet/use-socketio.svg?branch=master)](https://travis-ci.org/mfrachet/use-socketio) React hooks for handling server-push technologies: - [use-socketio](./packages/use-socketio) for [Socket.io](https://socket.io/) - [use-server-sent-events](./packages/use-server-sent-events) for [Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) - [use-websockets](./packages/use-websockets) for [Websocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) ## Running samples To run samples locally, you can: ```sh $ git clone https://github.com/mfrachet/server-push-hooks $ cd server-push-hooks $ npm install # install lerna and dependencies at the root $ npm run bootstrap # install lerna packages dependencies $ npm run build # build the lerna packages $ npm start # start the web application $ npm start:test-server # start the backend services in another terminal $ npm run e2e # run E2E tests of the projects in another terminal ``` ================================================ FILE: cypress/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io", "body": "Fixtures are a great way to mock data for responses to routes" } ================================================ FILE: cypress/integration/socketio.spec.js ================================================ context("Socket.io", () => { beforeEach(() => { cy.visit("http://localhost:1234/socket-io"); }); describe("Last message section", () => { it("should be an empty section at the beginning", () => { cy.get("[data-cy=last-message]").invoke("text").should("eq", ""); }); it("should display a message in the last message section", () => { cy.get("[data-cy=add-one-last-message]").click(); cy.get("[data-cy=last-message]") .invoke("text") .should("eq", "This is one new message"); }); it("should display only the last message in the last message section while triggering three", () => { cy.get("[data-cy=add-three-last-messages]").click(); cy.get("[data-cy=last-message]") .invoke("text") .should("eq", "This is a third new message"); }); it("should successfully manage subscription and unsubscription for a specific event", () => { cy.get("[data-cy=add-one-last-message]").click(); cy.wait(100); cy.get("[data-cy=unsubscribe-last-message]").click(); cy.get("[data-cy=add-three-last-messages]").click(); cy.get("[data-cy=last-message]") .invoke("text") .should("eq", "This is one new message"); cy.get("[data-cy=subscribe-last-message]").click(); cy.wait(100); cy.get("[data-cy=add-three-last-messages]").click(); cy.get("[data-cy=last-message]") .invoke("text") .should("eq", "This is a third new message"); }); }); describe('All "message" event messages', () => { beforeEach(() => { cy.get("[data-cy=clear-messages]").click(); }); it("should be an empty section at the beginning", () => { cy.get("[data-cy=messages]").invoke("text").should("eq", ""); }); it("should display 3 messages", () => { cy.get("[data-cy=add-three-messages]").click(); cy.wait(100); cy.get("[data-cy=messages]") .children("li") .eq(2) .invoke("text") .should("eq", "This is one new message"); cy.get("[data-cy=messages]") .children("li") .eq(1) .invoke("text") .should("eq", "This is a second new message"); cy.get("[data-cy=messages]") .children("li") .eq(0) .invoke("text") .should("eq", "This is a third new message"); }); it("should manage the subscription and unsubscription flows", () => { cy.get("[data-cy=add-three-messages]").click(); cy.wait(100); cy.get("[data-cy=messages]").children("li").its("length").should("eq", 3); cy.get("[data-cy=unsubscribe-messages").click(); cy.get("[data-cy=add-three-messages]").click(); cy.wait(100); cy.get("[data-cy=messages]").children("li").its("length").should("eq", 3); cy.get("[data-cy=subscribe-messages").click(); cy.get("[data-cy=add-three-messages]").click(); cy.wait(100); cy.get("[data-cy=messages]").children("li").its("length").should("eq", 6); }); }); }); ================================================ FILE: cypress/integration/sse.spec.js ================================================ context("Server sent event", () => { describe("Last message for SSE", () => { beforeEach(() => { cy.visit("http://localhost:1234/last-sse"); }); it("should be an empty section at the beginning", () => { cy.get("[data-cy=last-sse-message]").invoke("text").should("eq", ""); }); it("should have 'Marvin' as first SSE message", () => { cy.get("[data-cy=last-sse-message]").should("contain", "Marvin"); }); it("should have 'Laetitia' as second SSE message", () => { cy.get("[data-cy=last-sse-message]").should("contain", "Laetitia"); }); }); describe("All messages from SSE", () => { beforeEach(() => { cy.visit("http://localhost:1234/all-sse"); }); it("should display all the names", () => { cy.get("[data-cy=all-sse-messages]").should("contain", "Marvin"); cy.get("[data-cy=all-sse-messages]").should("contain", "Laetitia"); }); }); }); ================================================ FILE: cypress/integration/websocket.spec.js ================================================ context("Socket.io", () => { beforeEach(() => { cy.visit("http://localhost:1234/websocket"); cy.contains("Clear all messages").click(); }); describe("Connection", () => { it("should open a websocket connection", () => { cy.contains("Connection is opened.").should("be.visible"); }); }); describe("Last message section", () => { beforeEach(() => { cy.contains("Switch to Last message").click(); }); it("should be an empty section at the beginning", () => { cy.contains("No last messages").should("be.visible"); }); it("should show one last message", () => { cy.contains("Ask for one last message").click(); cy.contains("One last message").should("be.visible"); }); it("should show three last message", () => { cy.contains("Ask for three last messages").click(); cy.contains("Three last message of three").should("be.visible"); }); }); describe("All message section", () => { it("should be an empty section at the beginning", () => { cy.contains("No messages").should("be.visible"); }); it("should show three messages", () => { cy.contains("Ask for messages").click(); cy.contains("One of three messages").should("be.visible"); cy.contains("Two of three messages").should("be.visible"); cy.contains("Three of three messages").should("be.visible"); }); }); }); ================================================ FILE: cypress/plugins/index.js ================================================ // *********************************************************** // This example plugins/index.js can be used to load plugins // // You can change the location of this file or turn off loading // the plugins file with the 'pluginsFile' configuration option. // // You can read more here: // https://on.cypress.io/plugins-guide // *********************************************************** // This function is called when a project is opened or re-opened (e.g. due to // the project's config changing) module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config } ================================================ FILE: cypress/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: cypress/support/index.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: cypress.json ================================================ { "defaultCommandTimeout": 10000 } ================================================ FILE: example/App.js ================================================ import React from "react"; import { SocketIoExample } from "./socketio/SocketIo"; import { AllSSEMessagesExample, LastSSEMessageExample } from "./sse/SSE"; import { WebsocketExample } from "./websocket/Websocket"; import { Link, Router } from "@reach/router"; export const App = () => (
); ================================================ FILE: example/index.html ================================================ React starter app
================================================ FILE: example/index.js ================================================ import React from "react"; import ReactDOM from "react-dom"; import { App } from "./App"; const mountNode = document.getElementById("app"); ReactDOM.render(, mountNode); ================================================ FILE: example/server-helpers.js ================================================ const express = require("express"); const http = require("http"); const cors = require("cors"); const createHttpServer = () => { const app = express(); const server = http.createServer(app); app.use(cors()); return { server, app }; }; module.exports = createHttpServer; ================================================ FILE: example/server.js ================================================ const startSocketIoServer = require("./socketio/socketio-server"); const startSSEServer = require("./sse/sse-server"); const startWebsocketServer = require("./websocket/websocket-server"); startSocketIoServer(); startSSEServer(); startWebsocketServer(); ================================================ FILE: example/socketio/SocketIo.js ================================================ import React, { useEffect, useState } from "react"; import { SocketIOProvider, useLastMessage, useSocket, } from "../../packages/use-socketio"; import { port } from "./constants"; const LastMessage = () => { const [lastMessage, setLastMessage] = useState(); const { data, socket, unsubscribe, subscribe } = useLastMessage( "last-messages" ); // synchronize state with data so that we can clear it in the ui useEffect(() => { setLastMessage(data); }, [data]); return (

Last message

{lastMessage}

); }; const Message = () => { const [messages, setMessages] = useState([]); const { socket, subscribe, unsubscribe } = useSocket( "new-message", (newMessage) => setMessages([newMessage, ...messages]) ); return (

All tweets

); }; export const SocketIoExample = () => ( ); ================================================ FILE: example/socketio/constants.js ================================================ module.exports = { port: 3000, }; ================================================ FILE: example/socketio/socketio-server.js ================================================ const socketIo = require("socket.io"); const createHttpServer = require("../server-helpers"); const { port } = require("./constants"); const startSocketIoServer = () => { const { server: socketIoServer } = createHttpServer(); const io = socketIo(socketIoServer, { cors: { origin: "http://localhost:1234", methods: ["GET", "POST"] } }); io.on("connection", function (socket) { socket.on("one-last-message", () => { socket.emit("last-messages", "This is one new message"); }); socket.on("three-last-messages", () => { socket.emit("last-messages", "This is one new message"); socket.emit("last-messages", "This is a second new message"); socket.emit("last-messages", "This is a third new message"); }); socket.on("three-messages", () => { socket.emit("new-message", "This is one new message"); socket.emit("new-message", "This is a second new message"); socket.emit("new-message", "This is a third new message"); }); }); socketIoServer.listen(port, () => console.log(`[Socket.io] Started on port :${port}`) ); }; module.exports = startSocketIoServer; ================================================ FILE: example/sse/SSE.js ================================================ import React, { useState } from "react"; import { useLastSSE, useSSE, SSEProvider, } from "../../packages/use-server-sent-events"; import { port } from "./constants"; export const LastSSEMessage = () => { const { data } = useLastSSE(); return (

Last SSE Message

{data && data.name}

); }; export const AllSSEMessages = () => { const [names, setNames] = useState([]); useSSE((nextName) => setNames([...names, nextName])); return (

All SSE messages

); }; export const LastSSEMessageExample = () => { return ( ); }; export const AllSSEMessagesExample = () => { return ( ); }; ================================================ FILE: example/sse/constants.js ================================================ module.exports = { port: 3123, }; ================================================ FILE: example/sse/sse-server.js ================================================ const createHttpServer = require("../server-helpers"); const { port } = require("./constants"); const sseRequestHandler = (req, res) => { res.set("Content-Type", "text/event-stream"); const firstData = { name: "Marvin" }; res.write(`data: ${JSON.stringify(firstData)}`); res.write("\n\n"); setTimeout(() => { const secondData = { name: "Laetitia" }; res.write(`data: ${JSON.stringify(secondData)}`); res.write("\n\n"); }, 500); }; const startSSEServer = () => { const { server: sseServer, app: appSSE } = createHttpServer(); appSSE.get("/last-sse", sseRequestHandler); appSSE.get("/all-sse", sseRequestHandler); sseServer.listen(port, () => { console.log(`[SSE] Started on port :${port}`); }); }; module.exports = startSSEServer; ================================================ FILE: example/websocket/Websocket.js ================================================ import React from "react"; import { WebSocketProvider, useWebsocket, useLastWebsocketMessage, } from "../../packages/use-websockets"; import { port } from "./constants"; const LastMessage = () => { const { data, ws } = useLastWebsocketMessage(); return (

Last message

{data?.message || "No last messages"}
); }; const AllMessages = () => { const [messages, setMessages] = React.useState([]); const { ws } = useWebsocket((d) => setMessages([...messages, d.message])); return (

All messages

{messages.length === 0 && <>No messages} {messages.length > 0 && ( )}
); }; export const WebsocketExample = () => { const [isOpened, setIsOpened] = React.useState(false); const [tab, setTab] = React.useState("all"); return ( setIsOpened(true)} >

Connection is {isOpened ? "opened" : "closed"}.

{tab === "all" && } {tab !== "all" && }
); }; ================================================ FILE: example/websocket/constants.js ================================================ module.exports = { port: 3124, }; ================================================ FILE: example/websocket/websocket-server.js ================================================ const WebSocket = require("ws"); const createHttpServer = require("../server-helpers"); const { port } = require("./constants"); const startWebsocketServer = () => { const { server } = createHttpServer(); const wss = new WebSocket.Server({ server }); wss.on("connection", function connection(ws) { const sendMessage = (type, message) => { ws.send(JSON.stringify({ type, message })); }; ws.on("message", function incoming(message) { switch (message) { case "one-last": sendMessage("one-last", "One last message"); break; case "three-last-messages": sendMessage("three-last-messages", "One last message of three"); sendMessage("three-last-messages", "Two last message of three"); sendMessage("three-last-messages", "Three last message of three"); break; case "all-messages": sendMessage("all-messages", "One of three messages"); sendMessage("all-messages", "Two of three messages"); sendMessage("all-messages", "Three of three messages"); break; } }); ws.send("Opened from the server"); }); server.listen(port, () => { console.log(`[Websocket] Started on port :${port}`); }); }; module.exports = startWebsocketServer; ================================================ FILE: lerna.json ================================================ { "packages": [ "packages/*" ], "version": "2.1.1" } ================================================ FILE: package.json ================================================ { "name": "root", "private": true, "scripts": { "bootstrap": "lerna bootstrap", "e2e:ci": "cypress run", "e2e": "cypress open", "build": "lerna run build", "format": "lerna run format", "lint": "lerna run lint", "check:lint": "lerna run check:lint", "start": "parcel example/index.html", "start:test-server": "node ./example/server", "release": "npm i && npm run bootstrap && npm run check:lint && npm run build && lerna version && lerna publish" }, "devDependencies": { "@reach/router": "^1.3.4", "cors": "^2.8.5", "cypress": "^5.2.0", "express": "^4.17.1", "lerna": "^3.22.1", "parcel-bundler": "^1.12.4", "react": "^16.13.1", "react-dom": "^16.13.1", "socket.io": "^3.0.3", "ws": "^7.3.1" } } ================================================ FILE: packages/use-server-sent-events/.npmignore ================================================ example cypress src node_modules dist .cache ================================================ FILE: packages/use-server-sent-events/README.md ================================================ Use [Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) with React, in hooks (React v16.8+). # Usage ## Installation ```sh $ yarn add use-server-sent-events ``` ## In your code ### useSSE hook Listen to a specific event and trigger the according callback every time there's one. **This hooks doesn't trigger a re-render. You have to manage it yourself.** ```jsx import { SSEProvider, useSSE } from "use-server-sent-events"; const Parent = () => ( console.log("connection opened")} > ); const Children = () => { const [messages, setMessages] = useState([]); const error = useSSE((nextMessage) => setMessages([...messages, nextMessage]) ); return ( ); }; ``` ### useLastSSE hook Listen to the latest message received on a specific event name. **This hook triggers a re-render so you don't have to.** ```jsx import { SSEProvider, useLastSSE } from "use-server-sent-events"; const Parent = () => ( ); const Children = () => { const { data, error } = useLastSSE(); return

{data || "No message yet"}

; }; ``` ## Notes For example on how to implement a Server Sent Event server, you can take a look at the [Server Sent Event example folder](../../example/sse/sse-server.js). ================================================ FILE: packages/use-server-sent-events/package.json ================================================ { "name": "use-server-sent-events", "version": "2.1.0", "description": "React hooks for https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events", "main": "./lib/index.js", "author": "Marvin Frachet ", "license": "MIT", "private": false, "homepage": "https://github.com/mfrachet/server-push-hooks", "repository": { "type": "git", "url": "https://github.com/mfrachet/server-push-hooks" }, "keywords": [ "react", "context" ], "scripts": { "build": "yarn lint && tsc --outDir lib", "format": "prettier --write './src/**/*.tsx'", "lint": "tslint -c tslint.json 'src/**/**'", "check:lint": "tsc --noEmit && yarn lint" }, "devDependencies": { "@babel/core": "^7.11.6", "@babel/preset-env": "^7.11.5", "@babel/preset-react": "^7.10.4", "@types/react": "^16.9.49", "@types/react-dom": "^16.9.8", "prettier": "^2.1.2", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", "tslint-plugin-prettier": "^2.3.0", "typescript": "^4.0.3" }, "peerDependencies": { "react": "^16.13.1" } } ================================================ FILE: packages/use-server-sent-events/src/context.ts ================================================ import { createContext } from "react"; export const SSEContext = createContext(undefined); ================================================ FILE: packages/use-server-sent-events/src/hooks.ts ================================================ import { useContext, useEffect, useRef, useState } from "react"; import { SSEContext } from "./context"; export const useLastSSE = () => { const [data, setData] = useState(undefined); const [error, setError] = useState(undefined); const eventSource = useContext(SSEContext); useEffect(() => { eventSource.onmessage = (e) => { let message; try { message = JSON.parse(e.data); } catch { message = e.data; } setData(message); }; eventSource.onerror = (e) => { setError(e); }; }, []); return { data, error }; }; export const useSSE = (onMessage: (data: JSON) => void) => { const [error, setError] = useState(undefined); const onMessageRef = useRef(undefined); onMessageRef.current = onMessage; const eventSource = useContext(SSEContext); useEffect(() => { eventSource.onmessage = (e) => { let message; try { message = JSON.parse(e.data); } catch { message = e.data; } onMessageRef.current(message); }; eventSource.onerror = (e) => { setError(e); }; }, []); return error; }; ================================================ FILE: packages/use-server-sent-events/src/index.tsx ================================================ export * from "./context"; export * from "./provider"; export * from "./hooks"; ================================================ FILE: packages/use-server-sent-events/src/provider.tsx ================================================ import * as React from "react"; import { SSEContext } from "./context"; export interface ISSEProviderProps { url: string; opts?: EventSourceInit; onOpen?: (ev: Event) => void; } export const SSEProvider: React.FC = ({ url, opts, children, onOpen, }) => { const eventSourceRef = React.useRef(); const onOpenRef = React.useRef<(ev: Event) => void>(); if (!window) { return <>{children}; } if (!eventSourceRef.current) { eventSourceRef.current = new EventSource(url, opts); } onOpenRef.current = onOpen; React.useEffect(() => { if (onOpenRef?.current) { eventSourceRef.current.onopen = onOpenRef.current; } return () => { eventSourceRef?.current?.close(); }; }, []); return ( {children} ); }; ================================================ FILE: packages/use-server-sent-events/tsconfig.json ================================================ { "compilerOptions": { "declaration": true, "esModuleInterop": true, "jsx": "react", "module": "commonjs", "noImplicitAny": false, "noImplicitReturns": true, "skipLibCheck": true }, "exclude": [], "include": ["./src/**/*"] } ================================================ FILE: packages/use-server-sent-events/tslint.json ================================================ { "extends": [ "tslint:latest", "tslint-plugin-prettier", "tslint-config-prettier" ], "jsRules": {}, "rules": {}, "rulesDirectory": [] } ================================================ FILE: packages/use-socketio/.npmignore ================================================ example cypress src node_modules dist .cache ================================================ FILE: packages/use-socketio/README.md ================================================ Use [socket.io v3](https://socket.io/) with React, in hooks (React v16.8+). _If you want to use socket.io in v2, you might want to use the v2.0.4 of this package. The last commit related to the v2 version is [this one](https://github.com/mfrachet/server-push-hooks/tree/4636e16f6753c5a49a52b0091ec92fce44e9913b)._ # Usage ## Installation ```sh $ yarn add use-socketio ``` ## In your code ### useSocket hook Listen to a specific event and trigger the according callback every time there's one. **This hooks doesn't trigger a re-render. You have to manage it yourself.** ```jsx import { SocketIOProvider, useSocket } from "use-socketio"; const Twitter = () => { const [tweets, setTweet] = useState([]); const { socket, subscribe, unsubscribe } = useSocket("tweet", (newTweet) => setTweet([newTweet, ...tweets]) ); return tweets.length ? (
    {tweets.map((tweet) => (
  • {tweet.text}
  • ))}
) : (

Actually waiting for the websocket server...

); }; const App = () => ( ); ``` _The socketio options to pass to the provider are available here: https://socket.io/docs/client-api/#new-Manager-url-options._ ### useLastMessage hook Listen to the latest message received on a specific event name. **This hook triggers a re-render so you don't have to.** ```jsx import { SocketIOProvider, useLastMessage } from "use-socketio"; const Twitter = () => { const { data: lastMessage, socket, subscribe, unsubscribe } = useLastMessage( "tweet" ); return

{lastMessage || "Waiting for some tweets"}

; }; const App = () => ( ); ``` ## Notes For example on how to implement a Socket.io server, you can take a look at the [socket.io the example folder](../../example/socketio/socketio-server.js). ================================================ FILE: packages/use-socketio/package.json ================================================ { "name": "use-socketio", "version": "2.1.0", "description": "React hooks for https://socket.io/", "main": "./lib/index.js", "author": "Marvin Frachet ", "license": "MIT", "private": false, "homepage": "https://github.com/mfrachet/server-push-hooks", "repository": { "type": "git", "url": "https://github.com/mfrachet/server-push-hooks" }, "keywords": [ "react", "context" ], "scripts": { "build": "yarn lint && tsc --outDir lib", "format": "prettier --write './src/**/*.tsx'", "lint": "tslint -c tslint.json 'src/**/**'", "check:lint": "tsc --noEmit && yarn lint" }, "devDependencies": { "@babel/core": "^7.11.6", "@babel/preset-env": "^7.11.5", "@babel/preset-react": "^7.10.4", "@types/react": "^16.9.49", "@types/react-dom": "^16.9.8", "@types/socket.io-client": "^1.4.33", "prettier": "^2.1.2", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", "tslint-plugin-prettier": "^2.3.0", "typescript": "^4.0.3" }, "peerDependencies": { "react": "^16.13.1" }, "dependencies": { "socket.io-client": "^3.0.3" } } ================================================ FILE: packages/use-socketio/src/context.ts ================================================ import { createContext } from "react"; export const SocketIOContext = createContext(undefined); ================================================ FILE: packages/use-socketio/src/hooks.ts ================================================ import { useContext, useEffect, useRef, useState } from "react"; import { SocketIOContext } from "./context"; export const useSocket = ( eventKey?: string, callback?: (...args: any) => void ) => { const socket = useContext(SocketIOContext); const callbackRef = useRef(callback); callbackRef.current = callback; const socketHandlerRef = useRef(function() { if (callbackRef.current) { callbackRef.current.apply(this, arguments); } }); const subscribe = () => { if (eventKey) { socket.on(eventKey, socketHandlerRef.current); } }; const unsubscribe = () => { if (eventKey) { socket.removeListener(eventKey, socketHandlerRef.current); } }; useEffect(() => { subscribe(); return unsubscribe; }, [eventKey]); return { socket, unsubscribe, subscribe }; }; export const useLastMessage = (eventKey: string) => { const socket = useContext(SocketIOContext); const [data, setData] = useState(); const subscribe = () => { if (eventKey) { socket.on(eventKey, setData); } }; const unsubscribe = () => { if (eventKey) { socket.removeListener(eventKey, setData); } }; useEffect(() => { subscribe(); return unsubscribe; }, [eventKey]); return { data, socket, unsubscribe, subscribe }; }; ================================================ FILE: packages/use-socketio/src/index.tsx ================================================ export * from "./context"; export * from "./provider"; export * from "./hooks"; ================================================ FILE: packages/use-socketio/src/provider.tsx ================================================ import * as React from "react"; import io from "socket.io-client"; import { SocketIOContext } from "./context"; export interface ISocketIOProviderProps { url: string; opts?: SocketIOClient.ConnectOpts; } export const SocketIOProvider: React.FC = ({ url, opts, children, }) => { const socketRef = React.useRef(); if (typeof window === "undefined") { return <>{children}; } if (!socketRef.current) { socketRef.current = io(url, opts || {}); } return ( {children} ); }; ================================================ FILE: packages/use-socketio/tsconfig.json ================================================ { "compilerOptions": { "declaration": true, "esModuleInterop": true, "jsx": "react", "module": "commonjs", "noImplicitAny": false, "noImplicitReturns": true, "skipLibCheck": true }, "exclude": [], "include": ["./src/**/*"] } ================================================ FILE: packages/use-socketio/tslint.json ================================================ { "extends": [ "tslint:latest", "tslint-plugin-prettier", "tslint-config-prettier" ], "jsRules": {}, "rules": {}, "rulesDirectory": [] } ================================================ FILE: packages/use-websockets/.npmignore ================================================ example cypress src node_modules dist .cache ================================================ FILE: packages/use-websockets/README.md ================================================ Use [Websocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) with React, in hooks (React v16.8+). # Usage ## Installation ```sh $ yarn add use-websockets ``` ## In your code ### useWebsocket hook Listen to a specific event and trigger the according callback every time there's one. **This hooks doesn't trigger a re-render. You have to manage it yourself.** ```jsx import { WebsocketProvider, useWebsocket } from "use-websockets"; const Parent = () => ( console.log("connection opened")} > ); const Children = () => { const [messages, setMessages] = useState([]); const { ws, error } = useWebsocket((nextMessage) => setMessages([...messages, nextMessage]) ); return (
    {messages.map((msg, index) => (
  • {msg}
  • ))}
); }; ``` ### useLastWebsocketMessage hook Listen to the latest message received on a specific event name. **This hook triggers a re-render so you don't have to.** ```jsx import { WebsocketProvider, useLastWebsocketMessage } from "use-websockets"; const Parent = () => ( ); const Children = () => { const { data, error, ws } = useLastWebsocketMessage(); return

{data || "No message yet"}

; }; ``` ## Notes For example on how to implement a Websocket server, you can take a look at the [Websocket example folder](../../example/websocket/websocket-server.js). ================================================ FILE: packages/use-websockets/package.json ================================================ { "name": "use-websockets", "version": "2.1.1", "description": "React hooks for https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API", "main": "./lib/index.js", "author": "Marvin Frachet ", "license": "MIT", "private": false, "homepage": "https://github.com/mfrachet/server-push-hooks", "repository": { "type": "git", "url": "https://github.com/mfrachet/server-push-hooks" }, "keywords": [ "react", "context" ], "scripts": { "build": "yarn lint && tsc --outDir lib", "format": "prettier --write './src/**/*.tsx'", "lint": "tslint -c tslint.json 'src/**/**'", "check:lint": "tsc --noEmit && yarn lint" }, "devDependencies": { "@babel/core": "^7.11.6", "@babel/preset-env": "^7.11.5", "@babel/preset-react": "^7.10.4", "@types/react": "^16.9.49", "@types/react-dom": "^16.9.8", "prettier": "^2.1.2", "tslint": "^6.1.3", "tslint-config-prettier": "^1.18.0", "tslint-plugin-prettier": "^2.3.0", "typescript": "^4.0.3" }, "peerDependencies": { "react": "^16.13.1" } } ================================================ FILE: packages/use-websockets/src/context.ts ================================================ import { createContext } from "react"; export const WebsocketContext = createContext(undefined); ================================================ FILE: packages/use-websockets/src/hooks.ts ================================================ import { useContext, useEffect, useRef, useState } from "react"; import { WebsocketContext } from "./context"; export const useLastWebsocketMessage = () => { const [data, setData] = useState(undefined); const [error, setError] = useState(undefined); const ws = useContext(WebsocketContext); useEffect(() => { ws.onmessage = (e) => { let message; try { message = JSON.parse(e.data); } catch { message = e.data; } setData(message); }; ws.onerror = (e) => { setError(e); }; }, []); return { data, error, ws }; }; export const useWebsocket = (onMessage: (data: T) => void) => { const [error, setError] = useState(undefined); const onMessageRef = useRef(undefined); onMessageRef.current = onMessage; const ws = useContext(WebsocketContext); useEffect(() => { ws.onmessage = (event) => { let message; try { message = JSON.parse(event.data); } catch { message = event.data; } onMessageRef.current(message); }; ws.onerror = (e) => { setError(e); }; }, []); return { error, ws }; }; ================================================ FILE: packages/use-websockets/src/index.tsx ================================================ export * from "./context"; export * from "./provider"; export * from "./hooks"; ================================================ FILE: packages/use-websockets/src/provider.tsx ================================================ import * as React from "react"; import { WebsocketContext } from "./context"; export interface IWebSocketProviderProps { url: string; protocols?: string | string[]; onOpen?: (ev: Event) => void; } export const WebSocketProvider: React.FC = ({ url, protocols, children, onOpen, }) => { const ws = React.useRef(); const onOpenRef = React.useRef<(ev: Event) => void>(); if (!window) { return <>{children}; } if (!ws.current) { ws.current = new WebSocket(url, protocols); } onOpenRef.current = onOpen; React.useEffect(() => { if (onOpenRef?.current) { ws.current.onopen = onOpenRef.current; } return () => { ws?.current?.close(); }; }, []); return ( {children} ); }; ================================================ FILE: packages/use-websockets/tsconfig.json ================================================ { "compilerOptions": { "declaration": true, "esModuleInterop": true, "jsx": "react", "module": "commonjs", "noImplicitAny": false, "noImplicitReturns": true, "skipLibCheck": true }, "exclude": [], "include": ["./src/**/*"] } ================================================ FILE: packages/use-websockets/tslint.json ================================================ { "extends": [ "tslint:latest", "tslint-plugin-prettier", "tslint-config-prettier" ], "jsRules": {}, "rules": {}, "rulesDirectory": [] }