├── signalr.d.ts ├── socketio.d.ts ├── .prettierrc ├── example ├── signalr │ ├── .gitignore │ ├── libman.json │ ├── Enums │ │ └── JobType.cs │ ├── appsettings.Development.json │ ├── ViewModel │ │ ├── StopWorkVm.cs │ │ └── StartWorkVm.cs │ ├── Contract │ │ └── IChatService.cs │ ├── appsettings.json │ ├── Example.csproj.user │ ├── Hubs │ │ ├── Clients │ │ │ └── IChatClient.cs │ │ ├── AppHubBase.cs │ │ └── ChatHub.cs │ ├── Example.csproj │ ├── Program.cs │ ├── Service │ │ ├── ChatService.cs │ │ └── WorkerBackgroundService.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Controllers │ │ └── HomeController.cs │ └── Startup.cs ├── src │ ├── react-app-env.d.ts │ ├── index.tsx │ ├── app.tsx │ ├── services │ │ └── hub.ts │ ├── webSocket.tsx │ ├── socket.tsx │ └── signalR.tsx ├── public │ ├── static │ │ ├── media │ │ │ ├── logo.png │ │ │ ├── logo@2x.png │ │ │ ├── logo@3x.png │ │ │ └── launchscreen.png │ │ └── css │ │ │ └── error.css │ ├── manifest.json │ └── index.html ├── .babelrc ├── socket │ ├── index.js │ └── server.js ├── swagger.config.json ├── webSocket │ ├── index.js │ └── server.js ├── .gitignore ├── tsconfig.json ├── package.json └── config-overrides.js ├── src ├── socket │ ├── index.tsx │ ├── provider │ │ ├── index.native.ts │ │ ├── types.ts │ │ ├── providerNativeFactory.ts │ │ └── index.ts │ ├── utils.ts │ ├── hooks.ts │ ├── types.ts │ └── context.ts ├── webSocket │ ├── index.tsx │ ├── provider │ │ ├── index.native.ts │ │ ├── types.ts │ │ ├── providerNativeFactory.ts │ │ └── index.ts │ ├── types.ts │ ├── hooks.ts │ ├── context.ts │ └── utils.ts ├── index.ts ├── signalr │ ├── index.tsx │ ├── provider │ │ ├── index.native.ts │ │ ├── types.ts │ │ ├── providerNativeFactory.ts │ │ └── index.ts │ ├── hooks.ts │ ├── types.ts │ ├── utils.ts │ └── context.ts └── utils.ts ├── websocket.d.ts ├── .eslintignore ├── .gitattributes ├── babel.config.js ├── commitlint.config.js ├── .vscode └── settings.json ├── .npmignore ├── jest.config.js ├── .eslintrc.js ├── signalr.js ├── socketio.js ├── LICENSE ├── websocket.js ├── .gitignore ├── package.json ├── CODE_OF_CONDUCT.md ├── readme.md ├── tsconfig.json └── CHANGELOG.md /signalr.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/signalr"; 2 | -------------------------------------------------------------------------------- /socketio.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/socket"; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all" 3 | } 4 | -------------------------------------------------------------------------------- /example/signalr/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /obj 3 | /.vscode -------------------------------------------------------------------------------- /src/socket/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./context"; 2 | -------------------------------------------------------------------------------- /websocket.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/webSocket"; 2 | -------------------------------------------------------------------------------- /src/webSocket/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./context"; 2 | -------------------------------------------------------------------------------- /example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | lib/ 5 | build/ 6 | 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:babel-preset-react-app"], 3 | }; 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./signalr"; 2 | export * from "./socket"; 3 | export * from "./webSocket"; 4 | -------------------------------------------------------------------------------- /src/signalr/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./context"; 2 | export type { ILogger, LogLevel } from "@microsoft/signalr"; 3 | -------------------------------------------------------------------------------- /example/signalr/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true 4 | } 5 | -------------------------------------------------------------------------------- /example/public/static/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosseinmd/react-signalr/HEAD/example/public/static/media/logo.png -------------------------------------------------------------------------------- /example/public/static/media/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosseinmd/react-signalr/HEAD/example/public/static/media/logo@2x.png -------------------------------------------------------------------------------- /example/public/static/media/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosseinmd/react-signalr/HEAD/example/public/static/media/logo@3x.png -------------------------------------------------------------------------------- /example/public/static/media/launchscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosseinmd/react-signalr/HEAD/example/public/static/media/launchscreen.png -------------------------------------------------------------------------------- /src/socket/provider/index.native.ts: -------------------------------------------------------------------------------- 1 | import { providerNativeFactory as providerFactory } from "./providerNativeFactory"; 2 | 3 | export { providerFactory }; 4 | -------------------------------------------------------------------------------- /src/webSocket/provider/index.native.ts: -------------------------------------------------------------------------------- 1 | import { providerNativeFactory as providerFactory } from "./providerNativeFactory"; 2 | 3 | export { providerFactory }; 4 | -------------------------------------------------------------------------------- /example/signalr/Enums/JobType.cs: -------------------------------------------------------------------------------- 1 | namespace Example.Enums 2 | { 3 | public enum JobType 4 | { 5 | Programer = 1, 6 | Manager, 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/signalr/provider/index.native.ts: -------------------------------------------------------------------------------- 1 | import "react-native-get-random-values"; 2 | import { providerNativeFactory as providerFactory } from "./providerNativeFactory"; 3 | 4 | export { providerFactory }; 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .prettierrc 2 | .gitattributes 3 | .vscode 4 | .eslintignore 5 | .eslintrc.js 6 | .gitignore 7 | .prettierrc 8 | commitlint.config.js 9 | CODE_OF_CONDUCT.md 10 | __tests__/ 11 | examples/ 12 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": [ 4 | "babel-plugin-react-native-web", 5 | "babel-plugin-danger-remove-unused-import" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /example/socket/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | router.get("/", (req, res) => { 5 | res.send({ response: "I am alive" }).status(200); 6 | }); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /example/swagger.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dir": "./src/services", 3 | "hub": "http://localhost:5000/api/signalRTypes/getSignalrType.json", 4 | "language": "typescript", 5 | "ignore": { 6 | "headerParams": ["terminalId"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/webSocket/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | router.get("/", (req, res) => { 5 | res.send({ response: "I am alive" }).status(200); 6 | }); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /example/signalr/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDom from "react-dom"; 3 | import Todo from "./app"; 4 | 5 | const App = () => { 6 | return ; 7 | }; 8 | 9 | ReactDom.render(, document.getElementById("root")); 10 | -------------------------------------------------------------------------------- /example/signalr/ViewModel/StopWorkVm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Example.ViewModel 4 | { 5 | public class StopWorkVm 6 | { 7 | public DateTime Date { get; set; } 8 | public string Description { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/signalr/Contract/IChatService.cs: -------------------------------------------------------------------------------- 1 | using Example.ViewModel; 2 | using System.Threading.Tasks; 3 | 4 | namespace Example.Contract 5 | { 6 | public interface IChatService 7 | { 8 | Task StartWorkAsync(StartWorkVm message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "node", 3 | transform: { 4 | "^.+\\.(js)$": "babel-jest", 5 | }, 6 | preset: "ts-jest", 7 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 8 | testPathIgnorePatterns: ["node_modules", "lib"], 9 | }; 10 | -------------------------------------------------------------------------------- /example/signalr/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "App": { 3 | "SelfUrl": "", 4 | "CorsOrigins": "*", 5 | "SignalROrigins": "http://localhost:3000" 6 | }, 7 | "Logging": { 8 | "LogLevel": { 9 | "Default": "Information", 10 | "Microsoft": "Warning", 11 | "Microsoft.Hosting.Lifetime": "Information" 12 | } 13 | }, 14 | "AllowedHosts": "*" 15 | } 16 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-principal", 3 | "name": "react-principal", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/webSocket/types.ts: -------------------------------------------------------------------------------- 1 | import { ProviderProps } from "./provider"; 2 | 3 | export interface Context { 4 | /** Should not be a dynamic */ 5 | key: string; 6 | Provider: (Props: ProviderProps) => JSX.Element; 7 | connection: WebSocket | null; 8 | shareConnectionBetweenTab: boolean; 9 | invoke: (data: any) => void; 10 | useWebSocketEffect: (callback: (data?: any) => void) => void; 11 | } 12 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/signalr/Example.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectDebugger 5 | 6 | 7 | Example 8 | 9 | -------------------------------------------------------------------------------- /example/public/static/css/error.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body, 6 | html { 7 | width: 100%; 8 | height: 100%; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | body { 14 | display: flex; 15 | align-content: center; 16 | align-items: center; 17 | background-color: #1f2229; 18 | text-align: center; 19 | color: #ffffff; 20 | font-family: sans-serif; 21 | } 22 | 23 | #error-container { 24 | margin: auto; 25 | } 26 | -------------------------------------------------------------------------------- /example/signalr/Hubs/Clients/IChatClient.cs: -------------------------------------------------------------------------------- 1 | using Example.ViewModel; 2 | using Microsoft.AspNetCore.SignalR; 3 | using System.Threading.Tasks; 4 | 5 | namespace Example.Hubs.Clients 6 | { 7 | public interface IChatClient 8 | { 9 | [HubMethodName("hello")] 10 | Task Hello(string message); 11 | 12 | [HubMethodName("startwork")] 13 | Task StartWorkAsync(StartWorkVm message); 14 | 15 | [HubMethodName("stopwork")] 16 | Task StopWorkAsync(StopWorkVm message); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/signalr/ViewModel/StartWorkVm.cs: -------------------------------------------------------------------------------- 1 | using Example.Enums; 2 | using System; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Example.ViewModel 6 | { 7 | public class StartWorkVm 8 | { 9 | 10 | [Required] 11 | public JobType JobType { get; set; } 12 | 13 | [Required, MaxLength(100)] 14 | public string FirstName { get; set; } 15 | 16 | [Required, MaxLength(200)] 17 | public string LastName { get; set; } 18 | 19 | public DateTime BirthDate { get; set; } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/signalr/Hubs/AppHubBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace Example.Hubs 6 | { 7 | public abstract class AppHubBase : Hub where T : class 8 | { 9 | 10 | public async override Task OnConnectedAsync() 11 | { 12 | await base.OnConnectedAsync(); 13 | } 14 | 15 | public async override Task OnDisconnectedAsync(Exception exception) 16 | { 17 | 18 | await base.OnDisconnectedAsync(exception); 19 | } 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /example/signalr/Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /src/webSocket/provider/types.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, ReactNode } from "react"; 2 | 3 | export interface ProviderProps { 4 | /** Default is true */ 5 | connectEnabled?: boolean; 6 | dependencies?: DependencyList; 7 | onError?: (error?: Event | Error) => Promise; 8 | onClose?: (error?: CloseEvent) => void; 9 | url: string; 10 | children: ReactNode | ReactNode[]; 11 | onOpen?: (connection: WebSocket) => void; 12 | logger?: { 13 | log: Console["log"]; 14 | error: Console["error"]; 15 | } | null; 16 | /** 17 | * react-native only 18 | */ 19 | headers?: any; 20 | } 21 | -------------------------------------------------------------------------------- /example/signalr/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Example 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | extends: [ 5 | "prettier", 6 | "prettier/@typescript-eslint", 7 | "plugin:prettier/recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:react/recommended", 10 | "plugin:react-hooks/recommended", 11 | ], 12 | rules: { 13 | "@typescript-eslint/ban-ts-comment": "off", 14 | "@typescript-eslint/no-explicit-any": "off", 15 | "@typescript-eslint/explicit-module-boundary-types": "off", 16 | "@typescript-eslint/no-var-requires": "off", 17 | "react/prop-types": "off", 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/socket/utils.ts: -------------------------------------------------------------------------------- 1 | import { io, ManagerOptions, Socket, SocketOptions } from "socket.io-client"; 2 | import { DefaultEventsMap } from "socket.io/dist/typed-events"; 3 | 4 | function isConnectionConnecting( 5 | connection: Socket, 6 | ) { 7 | return connection.connected; 8 | } 9 | 10 | function createConnection( 11 | url: string, 12 | opts: Partial | undefined, 13 | ) { 14 | const connectionBuilder = io(url, opts); 15 | 16 | const connection = connectionBuilder; 17 | return connection; 18 | } 19 | 20 | export { isConnectionConnecting, createConnection }; 21 | -------------------------------------------------------------------------------- /example/signalr/Service/ChatService.cs: -------------------------------------------------------------------------------- 1 | using Example.Contract; 2 | using Example.Hubs; 3 | using Example.Hubs.Clients; 4 | using Example.ViewModel; 5 | using Microsoft.AspNetCore.SignalR; 6 | using System.Threading.Tasks; 7 | 8 | namespace Example.Service 9 | { 10 | public class ChatService : IChatService { 11 | private readonly IHubContext chatHub; 12 | 13 | public ChatService(IHubContext chatHub) 14 | { 15 | this.chatHub = chatHub; 16 | this.chatHub = chatHub; 17 | } 18 | 19 | public async Task StartWorkAsync(StartWorkVm message) 20 | { 21 | await chatHub.Clients.All.StartWorkAsync(message); 22 | } 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /example/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { SignalR } from "./signalR"; 3 | import { Socket } from "./socket"; 4 | import { WebSocket } from "./webSocket"; 5 | 6 | const App = () => { 7 | const [page, setIsSignalR] = useState<"SignalR" | "Socket" | "WebSocket">( 8 | "WebSocket", 9 | ); 10 | return ( 11 |
12 | 13 | 14 | 15 | {page === "SignalR" ? ( 16 | 17 | ) : page === "Socket" ? ( 18 | 19 | ) : ( 20 | 21 | )} 22 |
23 | ); 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /src/socket/provider/types.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList, ReactNode } from "react"; 2 | import { ManagerOptions, SocketOptions } from "socket.io-client"; 3 | 4 | export interface ProviderProps extends Partial { 5 | /** Default is true */ 6 | connectEnabled?: boolean; 7 | dependencies?: DependencyList; 8 | onError?: (error?: Error) => Promise; 9 | url: string; 10 | children: ReactNode | ReactNode[]; 11 | /** 12 | * A function that provides an access token required for HTTP Bearer authentication. 13 | * 14 | * @returns {string | Promise} A string containing the access 15 | * token, or a Promise that resolves to a string containing the access token. 16 | */ 17 | accessTokenFactory?(): string | Promise; 18 | automaticReconnect?: boolean; 19 | } 20 | -------------------------------------------------------------------------------- /src/webSocket/hooks.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useEffect } from "react"; 3 | import { useEvent } from "../utils"; 4 | import { Context } from "./types"; 5 | import { getMessageEvent } from "./utils"; 6 | 7 | function createUseSignalREffect(context: Context) { 8 | const useSignalREffect = (callback: (data: any) => void) => { 9 | const callbackRef = useEvent(callback); 10 | 11 | useEffect(() => { 12 | function _callback(data: any) { 13 | callbackRef(data); 14 | } 15 | 16 | hermes.on(getMessageEvent(context), _callback); 17 | 18 | return () => { 19 | hermes.off(getMessageEvent(context), _callback); 20 | }; 21 | // eslint-disable-next-line react-hooks/exhaustive-deps 22 | }, []); 23 | }; 24 | 25 | return useSignalREffect; 26 | } 27 | export { createUseSignalREffect }; 28 | -------------------------------------------------------------------------------- /example/signalr/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51207", 7 | "sslPort": 0 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "home", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "Example": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "home", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "http://localhost:5000" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useCallback, useRef } from "react"; 3 | 4 | const __DEV__ = process.env.NODE_ENV !== "production"; 5 | 6 | function removeDuplicates(arr: string[]) { 7 | const s = new Set(arr); 8 | const it = s.values(); 9 | return Array.from(it); 10 | } 11 | 12 | function sendWithHermes( 13 | event: string, 14 | message: any, 15 | shareConnectionBetweenTab: boolean, 16 | ) { 17 | hermes.send(event, message, shareConnectionBetweenTab ? "all" : "current"); 18 | } 19 | 20 | function useEvent any)>(prop: T): T { 21 | const ref = useRef(prop); 22 | if (ref.current !== prop) { 23 | ref.current = prop; 24 | } 25 | 26 | const callback = useCallback((...args: any[]) => { 27 | return ref.current!(...args); 28 | }, []) as T; 29 | 30 | return prop ? callback : prop; 31 | } 32 | 33 | export { removeDuplicates, sendWithHermes, useEvent, __DEV__ }; 34 | -------------------------------------------------------------------------------- /example/signalr/Service/WorkerBackgroundService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Example.Hubs; 5 | using Example.Hubs.Clients; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | public class Worker : BackgroundService 11 | { 12 | private readonly ILogger _logger; 13 | private readonly IHubContext _chatHub; 14 | 15 | public Worker(ILogger logger, IHubContext chatHub) 16 | { 17 | _logger = logger; 18 | _chatHub = chatHub; 19 | } 20 | 21 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 22 | { 23 | while (!stoppingToken.IsCancellationRequested) 24 | { 25 | await _chatHub.Clients.All.Hello(DateTime.Now.ToString()); 26 | await Task.Delay(1000, stoppingToken); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /example/signalr/Hubs/ChatHub.cs: -------------------------------------------------------------------------------- 1 | using Example.Hubs.Clients; 2 | using Example.ViewModel; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace Example.Hubs 7 | { 8 | public class ChatHub : AppHubBase , IHuB 9 | { 10 | public async Task Hello() 11 | { 12 | await Clients.All.Hello(DateTime.Now.ToString()); 13 | } 14 | 15 | public async Task StartWorkAsync(StartWorkVm message) 16 | { 17 | await Clients.All.StartWorkAsync(message); 18 | 19 | return "Server Response"; 20 | } 21 | 22 | public async Task StopWork(StopWorkVm message) 23 | { 24 | await Clients.All.StopWorkAsync(message); 25 | } 26 | 27 | public async Task StopWork2(string message) 28 | { 29 | await Clients.All.StopWorkAsync(new StopWorkVm()); 30 | } 31 | 32 | } 33 | 34 | public interface IHuB 35 | { 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/signalr/hooks.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useEffect } from "react"; 3 | import { Context, Hub } from "./types"; 4 | import { useEvent } from "../utils"; 5 | 6 | function createUseSignalREffect(context: Context) { 7 | const useSignalREffect = < 8 | E extends keyof T["callbacks"], 9 | C extends (...args: any) => void, 10 | >( 11 | event: E, 12 | callback: C, 13 | ) => { 14 | const callbackRef = useEvent(callback); 15 | useEffect(() => { 16 | function _callback(args: any[]) { 17 | callbackRef(...args); 18 | } 19 | 20 | context.on?.(event as string); 21 | hermes.on(event as string, _callback); 22 | 23 | return () => { 24 | context.off?.(event as string); 25 | hermes.off(event as string, _callback); 26 | }; 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, []); 29 | }; 30 | 31 | return useSignalREffect; 32 | } 33 | export { createUseSignalREffect }; 34 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "cd ../ && yarn build && cd ./example && react-app-rewired start", 7 | "start-servers": "concurrently 'yarn run-hub' 'yarn run-socket' 'yarn run-websocket'", 8 | "run-hub": "cd ./signalr && dotnet run cd ../", 9 | "run-socket": "node socket/server.js", 10 | "run-websocket": "node webSocket/server.js", 11 | "build": "react-app-rewired build", 12 | "test": "react-app-rewired test", 13 | "eject": "react-app-rewired eject" 14 | }, 15 | "browserslist": [ 16 | ">0.2%", 17 | "not dead", 18 | "not ie <= 11", 19 | "not op_mini all" 20 | ], 21 | "devDependencies": { 22 | "@types/react-dom": "^17.0.0", 23 | "concurrently": "^5.3.0", 24 | "express": "^4.17.1", 25 | "react-app-rewired": "^2.1.6", 26 | "socket.io": "^4.2.0", 27 | "ws": "^8.4.2", 28 | "swagger-typescript": "^4.2.1", 29 | "url-loader": "^4.1.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/socket/hooks.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useEffect } from "react"; 3 | import { Context, Hub } from "./types"; 4 | import { useEvent } from "../utils"; 5 | 6 | function createUseSocketEffect(context: Context) { 7 | const useSocketEffect = < 8 | E extends keyof T["callbacks"], 9 | C extends (...args: any) => void, 10 | >( 11 | event: E, 12 | callback: C, 13 | ) => { 14 | const callbackRef = useEvent(callback); 15 | useEffect(() => { 16 | function _callback(args: any[]) { 17 | callbackRef(...(Array.isArray(args) ? args : [args])); 18 | } 19 | 20 | context.on?.(event as string); 21 | hermes.on(event as string, _callback); 22 | 23 | return () => { 24 | context.off?.(event as string); 25 | hermes.off(event as string, _callback); 26 | }; 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, []); 29 | }; 30 | 31 | return useSocketEffect; 32 | } 33 | export { createUseSocketEffect }; 34 | -------------------------------------------------------------------------------- /example/webSocket/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const http = require("http"); 3 | const { WebSocketServer } = require("ws"); 4 | 5 | const port = process.env.PORT || 4002; 6 | const index = require("./index"); 7 | 8 | const app = express(); 9 | app.use(index); 10 | 11 | const server = http.createServer(app); 12 | 13 | const wss = new WebSocketServer({ port: 8081 }); 14 | 15 | wss.on("connection", (ws) => { 16 | console.log("New client connected"); 17 | 18 | ws.on("message", (event) => { 19 | ws.send(event.toString()); 20 | }); 21 | const interval = setInterval(() => getApiAndEmit(ws), 1000); 22 | ws.on("close", () => { 23 | console.log("Client disconnected"); 24 | clearInterval(interval); 25 | }); 26 | }); 27 | 28 | const getApiAndEmit = (socket) => { 29 | const response = new Date(); 30 | // Emitting a new message. Will be consumed by the client 31 | socket.send(JSON.stringify({ message: "hello", date: response })); 32 | }; 33 | 34 | server.listen(port, () => console.log(`Listening on port ${port}`)); 35 | -------------------------------------------------------------------------------- /src/signalr/types.ts: -------------------------------------------------------------------------------- 1 | import { HubConnection } from "@microsoft/signalr"; 2 | import { DependencyList } from "react"; 3 | import { ProviderProps } from "./provider"; 4 | 5 | export interface Context { 6 | Provider: (Props: ProviderProps) => JSX.Element; 7 | connection: HubConnection | null; 8 | shareConnectionBetweenTab: boolean; 9 | invoke: < 10 | E extends keyof T["methods"], 11 | C extends Parameters, 12 | R = any, 13 | >( 14 | methodName: E, 15 | ...args: C 16 | ) => Promise | undefined; 17 | useSignalREffect: < 18 | E extends keyof T["callbacks"], 19 | C extends T["callbacks"][E], 20 | >( 21 | events: E, 22 | callback: C, 23 | deps: DependencyList, 24 | ) => void; 25 | on?: (event: string) => void; 26 | off?: (event: string) => void; 27 | } 28 | 29 | export interface Hub { 30 | callbacks: { 31 | [name in T]: (...args: any[]) => void; 32 | }; 33 | methods: { 34 | [name in M]: (...args: any[]) => void; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/socket/types.ts: -------------------------------------------------------------------------------- 1 | import { DependencyList } from "react"; 2 | import { Socket } from "socket.io-client"; 3 | import { DefaultEventsMap } from "socket.io/dist/typed-events"; 4 | import { ProviderProps } from "./provider"; 5 | 6 | export interface Context { 7 | Provider: (Props: ProviderProps) => JSX.Element; 8 | connection: Socket | null; 9 | shareConnectionBetweenTab: boolean; 10 | invoke: >( 11 | methodName: E, 12 | ...args: C 13 | ) => void; 14 | useSocketEffect: < 15 | E extends keyof T["callbacks"], 16 | C extends T["callbacks"][E], 17 | >( 18 | events: E, 19 | callback: C, 20 | deps: DependencyList, 21 | ) => void; 22 | on?: (event: string) => void; 23 | off?: (event: string) => void; 24 | } 25 | 26 | export interface Hub { 27 | callbacks: { 28 | [name in T]: (...args: any[]) => void; 29 | }; 30 | methods: { 31 | [name in M]: (...args: any[]) => void; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /signalr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = 3 | (this && this.__createBinding) || 4 | (Object.create 5 | ? function (o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | var desc = Object.getOwnPropertyDescriptor(m, k); 8 | if ( 9 | !desc || 10 | ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) 11 | ) { 12 | desc = { 13 | enumerable: true, 14 | get: function () { 15 | return m[k]; 16 | }, 17 | }; 18 | } 19 | Object.defineProperty(o, k2, desc); 20 | } 21 | : function (o, m, k, k2) { 22 | if (k2 === undefined) k2 = k; 23 | o[k2] = m[k]; 24 | }); 25 | var __exportStar = 26 | (this && this.__exportStar) || 27 | function (m, exports) { 28 | for (var p in m) 29 | if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) 30 | __createBinding(exports, m, p); 31 | }; 32 | Object.defineProperty(exports, "__esModule", { value: true }); 33 | __exportStar(require("./lib/signalr"), exports); 34 | -------------------------------------------------------------------------------- /socketio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = 3 | (this && this.__createBinding) || 4 | (Object.create 5 | ? function (o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | var desc = Object.getOwnPropertyDescriptor(m, k); 8 | if ( 9 | !desc || 10 | ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) 11 | ) { 12 | desc = { 13 | enumerable: true, 14 | get: function () { 15 | return m[k]; 16 | }, 17 | }; 18 | } 19 | Object.defineProperty(o, k2, desc); 20 | } 21 | : function (o, m, k, k2) { 22 | if (k2 === undefined) k2 = k; 23 | o[k2] = m[k]; 24 | }); 25 | var __exportStar = 26 | (this && this.__exportStar) || 27 | function (m, exports) { 28 | for (var p in m) 29 | if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) 30 | __createBinding(exports, m, p); 31 | }; 32 | Object.defineProperty(exports, "__esModule", { value: true }); 33 | __exportStar(require("./lib/socket"), exports); 34 | -------------------------------------------------------------------------------- /src/signalr/provider/types.ts: -------------------------------------------------------------------------------- 1 | import { HubConnection, IHttpConnectionOptions } from "@microsoft/signalr"; 2 | import { DependencyList, ReactNode } from "react"; 3 | 4 | export interface ProviderProps extends IHttpConnectionOptions { 5 | /** Default is true */ 6 | connectEnabled?: boolean; 7 | dependencies?: DependencyList; 8 | onError?: (error?: Error) => Promise; 9 | url: string; 10 | children: ReactNode | ReactNode[]; 11 | onBeforeClose?: (connection: HubConnection) => Promise | void; 12 | onClosed?: (error?: Error) => void; 13 | onOpen?: (connection: HubConnection) => void; 14 | onReconnect?: (connection: HubConnection) => void; 15 | /** Configures the {@link @microsoft/signalr.HubConnection} to automatically attempt to reconnect if the connection is lost. 16 | * 17 | * `number[]` retryDelays An array containing the delays in milliseconds before trying each reconnect attempt. 18 | * The length of the array represents how many failed reconnect attempts it takes before the client will stop attempting to reconnect. 19 | */ 20 | automaticReconnect?: boolean | number[]; 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hossein Mohammadi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /websocket.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = 3 | (this && this.__createBinding) || 4 | (Object.create 5 | ? function (o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | var desc = Object.getOwnPropertyDescriptor(m, k); 8 | if ( 9 | !desc || 10 | ("get" in desc ? !m.__esModule : desc.writable || desc.configurable) 11 | ) { 12 | desc = { 13 | enumerable: true, 14 | get: function () { 15 | return m[k]; 16 | }, 17 | }; 18 | } 19 | Object.defineProperty(o, k2, desc); 20 | } 21 | : function (o, m, k, k2) { 22 | if (k2 === undefined) k2 = k; 23 | o[k2] = m[k]; 24 | }); 25 | var __exportStar = 26 | (this && this.__exportStar) || 27 | function (m, exports) { 28 | for (var p in m) 29 | if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) 30 | __createBinding(exports, m, p); 31 | }; 32 | Object.defineProperty(exports, "__esModule", { value: true }); 33 | __exportStar(require("./lib/webSocket"), exports); 34 | //# sourceMappingURL=index.js.map 35 | -------------------------------------------------------------------------------- /example/socket/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const http = require("http"); 3 | const socketIo = require("socket.io"); 4 | 5 | const port = process.env.PORT || 4001; 6 | const index = require("./index"); 7 | 8 | const app = express(); 9 | app.use(index); 10 | 11 | const server = http.createServer(app); 12 | 13 | const io = socketIo(server, { cors: { origin: "*" } }); 14 | 15 | let interval; 16 | 17 | io.on("connection", (socket) => { 18 | console.log("New client connected"); 19 | if (interval) { 20 | clearInterval(interval); 21 | } 22 | socket.on("StartWorkAsync", (event) => { 23 | console.log({ event }); 24 | socket.emit("Startwork", event); 25 | }); 26 | interval = setInterval(() => getApiAndEmit(socket), 1000); 27 | socket.on("disconnect", () => { 28 | console.log("Client disconnected"); 29 | clearInterval(interval); 30 | }); 31 | }); 32 | 33 | const getApiAndEmit = (socket) => { 34 | const response = new Date(); 35 | // Emitting a new message. Will be consumed by the client 36 | socket.emit("hello", response); 37 | }; 38 | 39 | server.listen(port, () => console.log(`Listening on port ${port}`)); 40 | -------------------------------------------------------------------------------- /example/signalr/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Example.Contract; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace Example.Controllers 7 | { 8 | [ApiController] 9 | [Route("[controller]")] 10 | public class HomeController : ControllerBase 11 | { 12 | 13 | private readonly IChatService _chatService; 14 | 15 | public HomeController(IChatService chatService) 16 | { 17 | _chatService = chatService; 18 | } 19 | 20 | [HttpGet("start")] 21 | public async Task Start() 22 | { 23 | await _chatService.StartWorkAsync(new ViewModel.StartWorkVm() 24 | { 25 | BirthDate = DateTime.Now, 26 | FirstName = "Majid", 27 | LastName = "Bigdeli", 28 | JobType = Enums.JobType.Manager 29 | }); 30 | return "started"; 31 | 32 | } 33 | [HttpGet("stop")] 34 | public async Task Stop() 35 | { 36 | // await _chatService.StopWork(new ViewModel.StopWorkVm() 37 | // { 38 | // Date = DateTime.Now, 39 | // Description = "Majid" 40 | // }); 41 | return ; 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | lib/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # os config 65 | .DS_Store 66 | 67 | # yarn 68 | yarn.lock 69 | 70 | # jest snapshots 71 | __snapshots__/ 72 | 73 | .vscode -------------------------------------------------------------------------------- /src/signalr/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HubConnection, 3 | HubConnectionBuilder, 4 | HubConnectionState, 5 | IHttpConnectionOptions, 6 | } from "@microsoft/signalr"; 7 | 8 | function isConnectionConnecting(connection: HubConnection) { 9 | return ( 10 | connection.state === HubConnectionState.Connected || 11 | connection.state === HubConnectionState.Reconnecting || 12 | connection.state === HubConnectionState.Connecting 13 | ); 14 | } 15 | 16 | function createConnection( 17 | url: string, 18 | transportType: IHttpConnectionOptions, 19 | automaticReconnect: boolean | number[] = true, 20 | ) { 21 | let connectionBuilder = new HubConnectionBuilder().withUrl( 22 | url, 23 | transportType, 24 | ); 25 | 26 | if (automaticReconnect) { 27 | if (Array.isArray(automaticReconnect)) { 28 | connectionBuilder = 29 | connectionBuilder.withAutomaticReconnect(automaticReconnect); 30 | } else { 31 | connectionBuilder = connectionBuilder.withAutomaticReconnect(); 32 | } 33 | } 34 | 35 | if (transportType.logger) { 36 | connectionBuilder = connectionBuilder.configureLogging( 37 | transportType.logger, 38 | ); 39 | } 40 | 41 | const connection = connectionBuilder.build(); 42 | 43 | return connection; 44 | } 45 | 46 | export { isConnectionConnecting, createConnection }; 47 | -------------------------------------------------------------------------------- /example/src/services/hub.ts: -------------------------------------------------------------------------------- 1 | export enum ChatOperationsNames { 2 | StartWorkAsync = "StartWorkAsync", 3 | StopWork = "StopWork", 4 | StopWork2 = "StopWork2", 5 | } 6 | 7 | export type ChatOperations = { 8 | [ChatOperationsNames.StartWorkAsync]: ( 9 | message?: StartWorkVm, 10 | ) => Promise; 11 | [ChatOperationsNames.StopWork]: (message?: StopWorkVm) => Promise; 12 | [ChatOperationsNames.StopWork2]: (message?: string) => Promise; 13 | }; 14 | 15 | export enum ChatCallbacksNames { 16 | hello = "hello", 17 | startwork = "Startwork", 18 | stopwork = "stopwork", 19 | } 20 | 21 | export type ChatCallbacks = { 22 | [ChatCallbacksNames.hello]: (message: string) => void; 23 | [ChatCallbacksNames.startwork]: (message?: StartWorkVm) => void; 24 | [ChatCallbacksNames.stopwork]: (message?: StopWorkVm) => void; 25 | }; 26 | 27 | export interface Chat { 28 | callbacks: ChatCallbacks; 29 | 30 | methods: ChatOperations; 31 | } 32 | 33 | export enum JobType { 34 | Programer = "Programer", 35 | Manager = "Manager", 36 | } 37 | 38 | export interface StartWorkVm { 39 | /** MaxLength: 100 */ 40 | firstName: string; 41 | jobType: JobType; 42 | /** MaxLength: 200 */ 43 | lastName: string; 44 | /** Format: date-time */ 45 | birthDate?: string; 46 | } 47 | 48 | export interface StopWorkVm { 49 | /** Format: date-time */ 50 | date?: string; 51 | description?: string; 52 | } 53 | -------------------------------------------------------------------------------- /example/src/webSocket.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { createWebSocketContext } from "../../websocket"; 3 | 4 | const SocketContext = createWebSocketContext({ 5 | key: "1", 6 | }); 7 | 8 | const WebSocket = () => { 9 | return ( 10 | token} 13 | // dependencies={[token]} //remove previous connection and create a new connection if changed 14 | url={"ws://127.0.0.1:8081"} 15 | > 16 | 17 | 18 | ); 19 | }; 20 | 21 | function Todo() { 22 | const [message, setMessage] = useState(""); 23 | 24 | SocketContext.useWebSocketEffect((message) => { 25 | setMessage(JSON.stringify(message)); 26 | }); 27 | 28 | return ( 29 |
37 |

React-signalr Websocket

38 | 50 |

{message}

51 |
52 | ); 53 | } 54 | 55 | export { WebSocket }; 56 | -------------------------------------------------------------------------------- /example/config-overrides.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | // const appDirectory = fs.realpathSync(process.cwd()) 5 | const resolveApp = (relativePath) => path.resolve(__dirname, relativePath); 6 | // our packages that will now be included in the CRA build step 7 | const appIncludes = [resolveApp("./src"), resolveApp("../src")]; 8 | 9 | module.exports = function override(config, env) { 10 | const __DEV__ = env !== "production"; 11 | 12 | // allow importing from outside of src folder 13 | config.resolve.plugins = config.resolve.plugins.filter( 14 | (plugin) => plugin.constructor.name !== "ModuleScopePlugin", 15 | ); 16 | config.module.rules[0].include = appIncludes; 17 | //eslint 18 | // if (__DEV__) { 19 | // config.module.rules[1].include = srcIncludes; 20 | // config.module.rules[1].use[0].options.baseConfig = require("./.eslintrc.json"); 21 | // } else { 22 | // config.module.rules[1] = null; 23 | // } 24 | // oneOf index will change to 2 in future 25 | config.module.rules[1].oneOf[3].include = appIncludes; 26 | // config.module.rules[1].oneOf[2].options.plugins = [ 27 | // require.resolve("babel-plugin-react-native-web"), 28 | // ].concat(config.module.rules[1].oneOf[2].options.plugins); 29 | // config.module.rules = config.module.rules.filter(Boolean); 30 | 31 | // config.plugins = config.plugins.filter((v) => !(v instanceof ESLintPlugin)); 32 | 33 | config.plugins.push(new webpack.DefinePlugin({ __DEV__ })); 34 | return config; 35 | }; 36 | -------------------------------------------------------------------------------- /src/webSocket/context.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { sendWithHermes } from "../utils"; 3 | import { createUseSignalREffect } from "./hooks"; 4 | import { providerFactory } from "./provider"; 5 | import { Context } from "./types"; 6 | import { providerNativeFactory } from "./provider/providerNativeFactory"; 7 | 8 | function createWebSocketContext(options: { 9 | key: string; 10 | shareConnectionBetweenTab?: boolean; 11 | }) { 12 | const SIGNAL_R_INVOKE = options.key + "SOCKET_INVOKE"; 13 | 14 | const context: Context = { 15 | key: options.key, 16 | connection: null, 17 | useWebSocketEffect: null as any, // Assigned after context 18 | shareConnectionBetweenTab: options?.shareConnectionBetweenTab || false, 19 | invoke: (data: any) => { 20 | if (!context.shareConnectionBetweenTab) { 21 | context.connection?.send(JSON.stringify(data)); 22 | return; 23 | } 24 | 25 | sendWithHermes(SIGNAL_R_INVOKE, data, context.shareConnectionBetweenTab); 26 | }, 27 | Provider: null as any, // just for ts ignore 28 | }; 29 | 30 | context.Provider = context.shareConnectionBetweenTab 31 | ? providerFactory(context) 32 | : providerNativeFactory(context); 33 | 34 | async function invoke(data: any) { 35 | context.connection?.send(JSON.stringify(data.data)); 36 | } 37 | 38 | context.useWebSocketEffect = createUseSignalREffect(context); 39 | 40 | hermes.on(SIGNAL_R_INVOKE, (data) => { 41 | if (context.connection?.readyState === WebSocket.OPEN) { 42 | invoke(data); 43 | } 44 | }); 45 | 46 | return context; 47 | } 48 | 49 | export { createWebSocketContext }; 50 | -------------------------------------------------------------------------------- /src/webSocket/utils.ts: -------------------------------------------------------------------------------- 1 | import { sendWithHermes } from "../utils"; 2 | import { ProviderProps } from "./provider/types"; 3 | import { Context } from "./types"; 4 | 5 | function isConnectionConnecting(connection: WebSocket | null) { 6 | return ( 7 | connection && 8 | (connection.readyState === WebSocket.CONNECTING || 9 | connection.readyState === WebSocket.OPEN) 10 | ); 11 | } 12 | 13 | function createConnection( 14 | context: Context, 15 | { 16 | url, 17 | onOpen, 18 | onClose, 19 | onErrorRef, 20 | logger, 21 | headers, 22 | }: Pick< 23 | ProviderProps, 24 | "url" | "onOpen" | "logger" | "onClose" | "headers" 25 | > & { 26 | onErrorRef: any; 27 | }, 28 | ) { 29 | let connection: WebSocket; 30 | if (headers) { 31 | //@ts-ignore 32 | connection = new WebSocket(url, null, { 33 | headers, 34 | }); 35 | } else { 36 | connection = new WebSocket(url); 37 | } 38 | 39 | connection.onopen = () => { 40 | onOpen?.(connection); 41 | logger?.log("webSocket connected"); 42 | }; 43 | 44 | connection.onmessage = (event) => { 45 | const data = event.data ? JSON.parse(event.data.toString()) : {}; 46 | 47 | sendWithHermes( 48 | getMessageEvent(context), 49 | data, 50 | context.shareConnectionBetweenTab, 51 | ); 52 | }; 53 | 54 | connection.onclose = (event) => { 55 | onClose?.(event); 56 | logger?.log("webSocket closed", event); 57 | }; 58 | 59 | connection.onerror = (error) => onErrorRef.current?.(error); 60 | 61 | context.connection = connection; 62 | 63 | return connection; 64 | } 65 | 66 | function getMessageEvent(context: Context) { 67 | return context.key + "WEB_SOCKET_EVENT"; 68 | } 69 | 70 | export { isConnectionConnecting, createConnection, getMessageEvent }; 71 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | react-concurrent 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 52 | 53 | 54 | 55 |
56 | 57 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/webSocket/provider/providerNativeFactory.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { useEvent } from "../../utils"; 3 | import { Context } from "../types"; 4 | import { createConnection, isConnectionConnecting } from "../utils"; 5 | import { ProviderProps } from "./types"; 6 | 7 | function providerNativeFactory(context: Context) { 8 | const Provider = ({ 9 | url, 10 | connectEnabled = true, 11 | children, 12 | dependencies = [], 13 | onError, 14 | onOpen, 15 | onClose, 16 | logger = console, 17 | headers, 18 | }: ProviderProps) => { 19 | const onErrorRef = useEvent(onError); 20 | // eslint-disable-next-line @typescript-eslint/no-empty-function 21 | const clear = useRef(() => {}); 22 | 23 | function refreshConnection() { 24 | if (!connectEnabled) { 25 | return; 26 | } 27 | 28 | async function checkForStart() { 29 | if (!isConnectionConnecting(context.connection)) { 30 | try { 31 | createConnection(context, { 32 | onClose, 33 | onOpen, 34 | logger, 35 | url, 36 | onErrorRef, 37 | headers, 38 | }); 39 | } catch (err) { 40 | console.log(err); 41 | onErrorRef?.(err as any); 42 | } 43 | } 44 | } 45 | 46 | checkForStart(); 47 | 48 | const checkInterval = setInterval(checkForStart, 6000); 49 | 50 | clear.current = () => { 51 | clearInterval(checkInterval); 52 | context.connection?.close(); 53 | }; 54 | } 55 | 56 | useState(() => { 57 | refreshConnection(); 58 | }); 59 | 60 | const isMounted = useRef(false); 61 | 62 | useEffect(() => { 63 | if (isMounted.current) { 64 | refreshConnection(); 65 | } 66 | 67 | isMounted.current = true; 68 | return () => { 69 | clear.current(); 70 | }; 71 | // eslint-disable-next-line react-hooks/exhaustive-deps 72 | }, [connectEnabled, url, ...dependencies]); 73 | 74 | return children as JSX.Element; 75 | }; 76 | return Provider; 77 | } 78 | 79 | export { providerNativeFactory }; 80 | -------------------------------------------------------------------------------- /example/src/socket.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from "react"; 2 | import { createSocketIoContext } from "../../socketio"; 3 | import { 4 | Chat, 5 | ChatCallbacksNames, 6 | ChatOperationsNames, 7 | JobType, 8 | } from "./services/hub"; 9 | 10 | const SocketContext = createSocketIoContext({ 11 | shareConnectionBetweenTab: true, 12 | }); 13 | 14 | const Socket = () => { 15 | return ( 16 | token} 19 | // dependencies={[token]} //remove previous connection and create a new connection if changed 20 | url={"http://127.0.0.1:4001"} 21 | > 22 | 23 | 24 | ); 25 | }; 26 | 27 | function Todo() { 28 | const [list, setList] = useReducer((state: string[] = [], action: string) => { 29 | return [action, ...state].slice(0, 200); 30 | }, []); 31 | 32 | SocketContext.useSocketEffect( 33 | ChatCallbacksNames.startwork, 34 | // eslint-disable-next-line @typescript-eslint/no-empty-function 35 | (message) => { 36 | setList("⬇ :" + JSON.stringify(message)); 37 | }, 38 | [], 39 | ); 40 | SocketContext.useSocketEffect( 41 | ChatCallbacksNames.hello, 42 | (message) => { 43 | setList("⬇ :" + JSON.stringify(message)); 44 | }, 45 | [], 46 | ); 47 | 48 | return ( 49 |
57 |

React socket.io

58 | 72 | {list.map((message, index) => ( 73 |

{message}

74 | ))} 75 |
76 | ); 77 | } 78 | 79 | export { Socket }; 80 | -------------------------------------------------------------------------------- /src/socket/provider/providerNativeFactory.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { useEvent } from "../../utils"; 3 | import { Context, Hub } from "../types"; 4 | import { createConnection, isConnectionConnecting } from "../utils"; 5 | import { ProviderProps } from "./types"; 6 | 7 | function providerNativeFactory(Context: Context) { 8 | const Provider = ({ 9 | url, 10 | connectEnabled = true, 11 | automaticReconnect = true, 12 | children, 13 | dependencies = [], 14 | accessTokenFactory, 15 | onError, 16 | ...rest 17 | }: ProviderProps) => { 18 | const onErrorRef = useEvent(onError); 19 | const accessTokenFactoryRef = useEvent(accessTokenFactory); 20 | // eslint-disable-next-line @typescript-eslint/no-empty-function 21 | const clear = useRef(() => {}); 22 | 23 | async function refreshConnection() { 24 | if (!connectEnabled) { 25 | return; 26 | } 27 | 28 | const connection = createConnection(url, { 29 | extraHeaders: { 30 | Authorization: (await accessTokenFactoryRef?.()) || "", 31 | }, 32 | reconnection: automaticReconnect, 33 | ...rest, 34 | }); 35 | 36 | Context.connection = connection; 37 | 38 | //@ts-ignore 39 | Context.reOn(); 40 | 41 | async function checkForStart() { 42 | if (!isConnectionConnecting(connection)) { 43 | try { 44 | await connection.open(); 45 | } catch (err) { 46 | console.log(err); 47 | onErrorRef?.(err as Error); 48 | } 49 | } 50 | } 51 | 52 | checkForStart(); 53 | 54 | const checkInterval = setInterval(checkForStart, 6000); 55 | 56 | clear.current = () => { 57 | clearInterval(checkInterval); 58 | connection.disconnect(); 59 | }; 60 | } 61 | 62 | useState(() => { 63 | refreshConnection(); 64 | }); 65 | 66 | const isMounted = useRef(false); 67 | 68 | useEffect(() => { 69 | if (isMounted.current) { 70 | refreshConnection(); 71 | } 72 | 73 | isMounted.current = true; 74 | return () => { 75 | clear.current(); 76 | }; 77 | // eslint-disable-next-line react-hooks/exhaustive-deps 78 | }, [connectEnabled, url, ...dependencies]); 79 | 80 | return children as JSX.Element; 81 | }; 82 | return Provider; 83 | } 84 | 85 | export { providerNativeFactory }; 86 | -------------------------------------------------------------------------------- /src/socket/context.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { removeDuplicates, sendWithHermes } from "../utils"; 3 | import { createUseSocketEffect } from "./hooks"; 4 | import { providerFactory } from "./provider"; 5 | import { Context, Hub } from "./types"; 6 | import { providerNativeFactory } from "./provider/providerNativeFactory"; 7 | 8 | const SOCEKT_IO_SEND = "SOCEKT_IO_SEND"; 9 | 10 | function createSocketIoContext(options?: { 11 | shareConnectionBetweenTab?: boolean; 12 | }) { 13 | const events: (keyof T["callbacks"])[] = []; 14 | const context: Context = { 15 | connection: null, 16 | useSocketEffect: null as any, // Assigned after context 17 | shareConnectionBetweenTab: options?.shareConnectionBetweenTab || false, 18 | invoke: (methodName, ...args: any[]) => { 19 | if (!context.shareConnectionBetweenTab) { 20 | context.connection?.emit(methodName as string, ...args); 21 | return; 22 | } 23 | sendWithHermes( 24 | SOCEKT_IO_SEND, 25 | { methodName, args }, 26 | context.shareConnectionBetweenTab, 27 | ); 28 | }, 29 | Provider: null as any, // just for ts ignore 30 | on: (event: string) => { 31 | if (!events.includes(event)) { 32 | context.connection?.on(event, (message: any) => { 33 | sendWithHermes(event, message, context.shareConnectionBetweenTab); 34 | }); 35 | } 36 | 37 | events.push(event); 38 | }, 39 | off: (event: string) => { 40 | if (events.includes(event)) { 41 | events.splice(events.indexOf(event), 1); 42 | } 43 | 44 | if (!events.includes(event)) { 45 | context.connection?.off(event); 46 | } 47 | }, 48 | //@ts-ignore 49 | reOn: () => { 50 | const uniqueEvents = removeDuplicates(events as string[]); 51 | 52 | uniqueEvents.forEach((event) => { 53 | context.connection?.on(event, (message: any) => { 54 | sendWithHermes(event, message, context.shareConnectionBetweenTab); 55 | }); 56 | }); 57 | }, 58 | }; 59 | 60 | context.Provider = context.shareConnectionBetweenTab 61 | ? providerFactory(context) 62 | : providerNativeFactory(context); 63 | context; 64 | 65 | context.useSocketEffect = createUseSocketEffect(context); 66 | 67 | hermes.on(SOCEKT_IO_SEND, (data) => { 68 | if (context.connection?.connected) { 69 | context.connection.emit(data.methodName, ...data.args); 70 | } 71 | }); 72 | 73 | return context; 74 | } 75 | 76 | export { createSocketIoContext }; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-signalr", 3 | "version": "0.2.24", 4 | "description": "React-signalr is a tools for using signalr web socket in react/react-native apps", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "compile": "tsc", 9 | "prepare": "yarn build", 10 | "build": "rm -rf ./lib && yarn compile", 11 | "lint": "eslint --ext '.ts' ./src", 12 | "test": "yarn build && jest", 13 | "release": " yarn build && standard-version && npm publish && git push --follow-tags origin main", 14 | "prettierAll": "prettier --write ." 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/hosseinmd/react-signalr.git" 19 | }, 20 | "keywords": [ 21 | "react-signalr", 22 | "react", 23 | "signalr", 24 | "web socket", 25 | "socket", 26 | "microsoft" 27 | ], 28 | "author": "Hossein mohammadi", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/hosseinmd/react-signalr/issues" 32 | }, 33 | "homepage": "https://github.com/hosseinmd/react-signalr#readme", 34 | "dependencies": { 35 | "hermes-channel": "^2.1.2", 36 | "js-cookie": "^2.2.1", 37 | "uuid": "^8.3.2" 38 | }, 39 | "peerDependencies": { 40 | "react": ">=16.13", 41 | "@microsoft/signalr": "^8.0.0", 42 | "socket.io-client": "^4.2.0" 43 | }, 44 | "devDependencies": { 45 | "socket.io-client": "^4.2.0", 46 | "socket.io": "^4.2.0", 47 | "@microsoft/signalr": "^8.0.0", 48 | "@commitlint/config-conventional": "^11.0.0", 49 | "@react-native-community/eslint-config": "^2.0.0", 50 | "@types/jest": "^26.0.15", 51 | "@types/js-cookie": "^2.2.6", 52 | "@types/react-dom": "^18.0.6", 53 | "@types/react": "^18.0.15", 54 | "@types/uuid": "^8.3.1", 55 | "@typescript-eslint/eslint-plugin": "^6.6.0", 56 | "@typescript-eslint/parser": "^6.6.0", 57 | "babel-jest": "^26.6.3", 58 | "commitlint": "^11.0.0", 59 | "eslint-plugin-prettier": "^5.0.0", 60 | "eslint-plugin-react-hooks": "^4.6.0", 61 | "eslint": "^8.48.0", 62 | "husky": "^4.3.0", 63 | "jest": "^26.6.3", 64 | "prettier-plugin-jsdoc": "^1.0.1", 65 | "prettier": "^3.0.3", 66 | "react-art": "^18.2.0", 67 | "react-dom": "^18.2.0", 68 | "react-scripts": "^5.0.1", 69 | "react-test-renderer": "^18.2.0", 70 | "react": "^18.2.0", 71 | "standard-version": "^9.0.0", 72 | "ts-jest": "^26.4.4", 73 | "typescript": "^5.2.2" 74 | }, 75 | "husky": { 76 | "hooks": { 77 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 78 | "pre-commit": "yarn lint && yarn compile --noEmit" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/src/signalR.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from "react"; 2 | import { createSignalRContext } from "../../signalr"; 3 | import { 4 | Chat, 5 | ChatCallbacksNames, 6 | ChatOperationsNames, 7 | JobType, 8 | } from "./services/hub"; 9 | 10 | const SignalRContext = createSignalRContext({ 11 | shareConnectionBetweenTab: true, 12 | }); 13 | 14 | const SignalR = () => { 15 | return ( 16 | token} 19 | // dependencies={[token]} //remove previous connection and create a new connection if changed 20 | url={"http://localhost:5000/hub"} 21 | onOpen={() => console.log("open")} 22 | onBeforeClose={() => 23 | new Promise((resolve) => { 24 | console.log("before close"); 25 | setTimeout(() => { 26 | resolve(); 27 | }, 1000); 28 | }) 29 | } 30 | onClosed={() => console.log("close", SignalRContext.connection?.state)} 31 | > 32 | 33 | 34 | ); 35 | }; 36 | 37 | function Todo() { 38 | const [list, setList] = useReducer((state: string[] = [], action: string) => { 39 | return [action, ...state].slice(0, 200); 40 | }, []); 41 | SignalRContext.useSignalREffect( 42 | ChatCallbacksNames.startwork, 43 | // eslint-disable-next-line @typescript-eslint/no-empty-function 44 | (message) => { 45 | setList("⬇ :" + JSON.stringify(message)); 46 | }, 47 | [], 48 | ); 49 | 50 | SignalRContext.useSignalREffect( 51 | ChatCallbacksNames.hello, 52 | // eslint-disable-next-line @typescript-eslint/no-empty-function 53 | (message) => { 54 | setList("⬇ :" + JSON.stringify(message)); 55 | }, 56 | [], 57 | ); 58 | 59 | async function invoke() { 60 | const message = { 61 | firstName: "h", 62 | lastName: "m", 63 | jobType: JobType.Programer, 64 | birthDate: new Date().toISOString(), 65 | } as const; 66 | setList("⬆ :" + JSON.stringify(message)); 67 | 68 | const response = await SignalRContext.invoke( 69 | ChatOperationsNames.StartWorkAsync, 70 | message, 71 | ); 72 | setList("⬇ :" + JSON.stringify(response)); 73 | } 74 | 75 | return ( 76 |
84 |

React signalR

85 | 86 | {list.map((message, index) => ( 87 |

{message}

88 | ))} 89 |
90 | ); 91 | } 92 | export { SignalR }; 93 | -------------------------------------------------------------------------------- /src/signalr/provider/providerNativeFactory.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { useEvent } from "../../utils"; 3 | import { Context, Hub } from "../types"; 4 | import { createConnection, isConnectionConnecting } from "../utils"; 5 | import { ProviderProps } from "./types"; 6 | 7 | function providerNativeFactory(Context: Context) { 8 | const Provider = ({ 9 | url, 10 | connectEnabled = true, 11 | automaticReconnect = true, 12 | children, 13 | dependencies = [], 14 | accessTokenFactory, 15 | onError, 16 | onOpen, 17 | onClosed, 18 | onReconnect, 19 | onBeforeClose, 20 | ...rest 21 | }: ProviderProps) => { 22 | const onErrorRef = useEvent(onError); 23 | const accessTokenFactoryRef = useEvent(accessTokenFactory); 24 | // eslint-disable-next-line @typescript-eslint/no-empty-function 25 | const clear = useRef(() => {}); 26 | 27 | function refreshConnection() { 28 | if (!connectEnabled) { 29 | return; 30 | } 31 | 32 | const connection = createConnection( 33 | url, 34 | { 35 | accessTokenFactory: () => accessTokenFactoryRef?.() || "", 36 | ...rest, 37 | }, 38 | automaticReconnect, 39 | ); 40 | connection.onreconnecting((error) => onErrorRef?.(error)); 41 | connection.onreconnected(() => onReconnect?.(connection)); 42 | 43 | Context.connection = connection; 44 | //@ts-ignore 45 | Context.reOn(); 46 | 47 | connection.onclose((error) => { 48 | onClosed?.(error); 49 | }); 50 | 51 | async function checkForStart() { 52 | if (!isConnectionConnecting(connection)) { 53 | try { 54 | await connection.start(); 55 | onOpen?.(connection); 56 | } catch (err) { 57 | console.log(err); 58 | onErrorRef?.(err as Error); 59 | } 60 | } 61 | } 62 | 63 | checkForStart(); 64 | 65 | const checkInterval = setInterval(checkForStart, 6000); 66 | 67 | clear.current = async () => { 68 | clearInterval(checkInterval); 69 | await onBeforeClose?.(connection); 70 | connection.stop(); 71 | }; 72 | } 73 | 74 | useState(() => { 75 | refreshConnection(); 76 | }); 77 | 78 | const isMounted = useRef(false); 79 | 80 | useEffect(() => { 81 | if (isMounted.current) { 82 | refreshConnection(); 83 | } 84 | 85 | isMounted.current = true; 86 | return () => { 87 | clear.current(); 88 | }; 89 | // eslint-disable-next-line react-hooks/exhaustive-deps 90 | }, [connectEnabled, url, ...dependencies]); 91 | 92 | return children as JSX.Element; 93 | }; 94 | return Provider; 95 | } 96 | 97 | export { providerNativeFactory }; 98 | -------------------------------------------------------------------------------- /src/signalr/context.ts: -------------------------------------------------------------------------------- 1 | import { HubConnectionState } from "@microsoft/signalr"; 2 | import hermes from "hermes-channel"; 3 | import { removeDuplicates, sendWithHermes } from "../utils"; 4 | import { createUseSignalREffect } from "./hooks"; 5 | import { providerFactory } from "./provider"; 6 | import { Context, Hub } from "./types"; 7 | import { v4 as uuid } from "uuid"; 8 | import { providerNativeFactory } from "./provider/providerNativeFactory"; 9 | 10 | const SIGNAL_R_INVOKE = "SIGNAL_R_INVOKE"; 11 | function createSignalRContext(options?: { 12 | shareConnectionBetweenTab?: boolean; 13 | }) { 14 | const events: (keyof T["callbacks"])[] = []; 15 | const context: Context = { 16 | connection: null, 17 | useSignalREffect: null as any, // Assigned after context 18 | shareConnectionBetweenTab: options?.shareConnectionBetweenTab || false, 19 | invoke(methodName, ...args: any[]): Promise | undefined { 20 | if (!context.shareConnectionBetweenTab) { 21 | return context.connection?.invoke(methodName as string, ...args); 22 | } 23 | 24 | const SIGNAL_R_RESPONSE = uuid(); 25 | sendWithHermes( 26 | SIGNAL_R_INVOKE, 27 | { methodName, args, callbackResponse: SIGNAL_R_RESPONSE }, 28 | context.shareConnectionBetweenTab, 29 | ); 30 | return new Promise((resolve) => { 31 | hermes.on(SIGNAL_R_RESPONSE, (data) => { 32 | resolve(data); 33 | }); 34 | }); 35 | }, 36 | Provider: null as any, // just for ts ignore 37 | on: (event: string) => { 38 | if (!events.includes(event)) { 39 | context.connection?.on(event, (...message: any) => { 40 | sendWithHermes(event, message, context.shareConnectionBetweenTab); 41 | }); 42 | } 43 | events.push(event); 44 | }, 45 | off: (event: string) => { 46 | if (events.includes(event)) { 47 | events.splice(events.indexOf(event), 1); 48 | } 49 | 50 | if (!events.includes(event)) { 51 | context.connection?.off(event); 52 | } 53 | }, 54 | //@ts-ignore 55 | reOn: () => { 56 | const uniqueEvents = removeDuplicates(events as string[]); 57 | 58 | uniqueEvents.forEach((event) => { 59 | context.connection?.on(event, (...message: any) => { 60 | sendWithHermes(event, message, context.shareConnectionBetweenTab); 61 | }); 62 | }); 63 | }, 64 | }; 65 | 66 | context.Provider = context.shareConnectionBetweenTab 67 | ? providerFactory(context) 68 | : providerNativeFactory(context); 69 | 70 | async function invoke(data: { 71 | methodName: string; 72 | args: any[]; 73 | callbackResponse: string; 74 | }) { 75 | const response = await context.connection?.invoke( 76 | data.methodName, 77 | ...data.args, 78 | ); 79 | sendWithHermes( 80 | data.callbackResponse, 81 | response, 82 | context.shareConnectionBetweenTab, 83 | ); 84 | } 85 | 86 | context.useSignalREffect = createUseSignalREffect(context); 87 | 88 | hermes.on(SIGNAL_R_INVOKE, (data) => { 89 | if (context.connection?.state === HubConnectionState.Connected) { 90 | invoke(data); 91 | } 92 | }); 93 | 94 | return context; 95 | } 96 | 97 | export { createSignalRContext }; 98 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at hosseinm.developer@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /example/signalr/Startup.cs: -------------------------------------------------------------------------------- 1 | using Example.Contract; 2 | using Example.Hubs; 3 | using Example.Service; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Newtonsoft.Json; 11 | using Newtonsoft.Json.Converters; 12 | using Newtonsoft.Json.Serialization; 13 | using Septa.AspNetCore.SignalRTypes; 14 | using System; 15 | using System.Collections.Generic; 16 | using System.Linq; 17 | using System.Reflection; 18 | 19 | namespace Example 20 | { 21 | public class Startup 22 | { 23 | private const string DefaultCorsPolicyName = "Default"; 24 | public Startup(IConfiguration configuration) 25 | { 26 | Configuration = configuration; 27 | } 28 | 29 | public IConfiguration Configuration { get; } 30 | 31 | // This method gets called by the runtime. Use this method to add services to the container. 32 | public void ConfigureServices(IServiceCollection services) 33 | { 34 | services.AddCors(options => 35 | { 36 | options.AddPolicy(DefaultCorsPolicyName, builder => 37 | { 38 | builder 39 | .WithOrigins( 40 | Configuration["App:CorsOrigins"] 41 | .Split(",", StringSplitOptions.RemoveEmptyEntries) 42 | .Select(o => o.TrimEnd('/')) 43 | .ToArray() 44 | ) 45 | .AllowAnyHeader() 46 | .AllowAnyMethod(); 47 | }); 48 | }); 49 | services.AddScoped(); 50 | services.AddHostedService(); 51 | services.AddSignalR() 52 | .AddNewtonsoftJsonProtocol(options => 53 | { 54 | options.PayloadSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 55 | options.PayloadSerializerSettings.NullValueHandling = NullValueHandling.Ignore; 56 | options.PayloadSerializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore; 57 | options.PayloadSerializerSettings.Converters.Add(new StringEnumConverter()); 58 | } 59 | ); 60 | 61 | services.AddSignalRTypes(); 62 | 63 | services.AddControllers().AddNewtonsoftJson(options => 64 | options.SerializerSettings.Converters.Add(new StringEnumConverter())); 65 | 66 | } 67 | 68 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 69 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 70 | { 71 | if (env.IsDevelopment()) 72 | { 73 | app.UseDeveloperExceptionPage(); 74 | } 75 | app.UseSignalrType(x => 76 | { 77 | x.RoutePath = "/api/signalRTypes/getSignalrType.json"; 78 | }); 79 | app.UseRouting(); 80 | app.UseCors(DefaultCorsPolicyName); 81 | app.UseAuthorization(); 82 | app.UseEndpoints(endpoints => 83 | { 84 | endpoints.MapControllers(); 85 | endpoints.MapHub("/hub").RequireCors(builder => 86 | { 87 | builder 88 | .WithOrigins(Configuration.GetSection("App:SignalROrigins").Value) 89 | .AllowAnyHeader() 90 | .AllowCredentials() 91 | .WithMethods("GET", "POST"); 92 | }); 93 | }); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React-Signalr 2 | 3 | [![NPM](https://nodei.co/npm/react-signalr.png)](https://nodei.co/npm/react-signalr/) 4 | 5 | [![install size](https://packagephobia.now.sh/badge?p=react-signalr)](https://packagephobia.now.sh/result?p=react-signalr) [![dependencies](https://david-dm.org/hosseinmd/react-signalr.svg)](https://david-dm.org/hosseinmd/react-signalr.svg) 6 | 7 | ## React-Signalr Is a tools for using signalR, Socket.io or WebSocket in react/react-native apps 8 | 9 | - Supported microsoft/signalR version 5 and later 10 | - Supported Socket.io 11 | - Supported WebSocket 12 | 13 | Features 14 | 15 | - Hooks for connect event to a component 16 | - Manage connections in multiple tabs (SignalR can only have about 6 tabs open). React-signalr will create a connection open and send event to other tabs by [hermes-channel](https://github.com/hosseinmd/hermes) 17 | - Handle reconnect 18 | 19 | ## TOC 20 | 21 | - [Install](#install) 22 | - [Get started SignalR](#signalr) 23 | - [Get started SocketIO](#socketio) 24 | - [Get started WebSocket](#websocket) 25 | 26 | ## install 27 | 28 | `$ yarn add react-signalr @microsoft/signalr socket.io-client` 29 | 30 | ## Get started 31 | 32 | ## signalr 33 | 34 | First of all you need to create a signalR context. every thing is depend on your context, you could create multiple context. 35 | 36 | ```js 37 | import { createSignalRContext } from "react-signalr/signalr"; 38 | 39 | const SignalRContext = createSignalRContext(); 40 | 41 | const App = () => { 42 | const { token } = YourAccessToken; 43 | 44 | return ( 45 | token} 48 | dependencies={[token]} //remove previous connection and create a new connection if changed 49 | url={"https://example/hub"} 50 | > 51 | 52 | 53 | ); 54 | }; 55 | ``` 56 | 57 | #### useSignalREffect 58 | 59 | Use this for connect to an event 60 | 61 | ```js 62 | const Comp = () => { 63 | const [messages, setMessage] = useState([]); 64 | 65 | SignalRContext.useSignalREffect( 66 | "event name", // Your Event Key 67 | (message) => { 68 | setMessage([...messages, message]); 69 | }, 70 | ); 71 | 72 | return ; 73 | }; 74 | ``` 75 | 76 | ## socketio 77 | 78 | create a socketIO context, 79 | 80 | ```js 81 | import { createSocketIoContext } from "react-signalr/socketio"; 82 | 83 | const SocketIOContext = createSocketIoContext(); 84 | 85 | const App = () => { 86 | const { token } = YourAccessToken; 87 | 88 | return ( 89 | token} 92 | dependencies={[token]} //remove previous connection and create a new connection if changed 93 | url={"https://example/hub"} 94 | > 95 | 96 | 97 | ); 98 | }; 99 | ``` 100 | 101 | #### useSignalREffect 102 | 103 | Use this to connect to an event 104 | 105 | ```js 106 | const Comp = () => { 107 | const [messages, setMessage] = useState([]); 108 | 109 | SocketIOContext.useSocketEffect( 110 | "event name", // Your Event Key 111 | (message) => { 112 | setMessage([...messages, message]); 113 | }, 114 | ); 115 | 116 | return ; 117 | }; 118 | ``` 119 | 120 | ## websocket 121 | 122 | create a websocket context, 123 | 124 | ```js 125 | import { createWebSocketContext } from "react-signalr/websocket"; 126 | 127 | const WebsocketContext = createWebSocketContext(); 128 | 129 | const App = () => { 130 | const { token } = YourAccessToken; 131 | 132 | return ( 133 | 138 | 139 | 140 | ); 141 | }; 142 | ``` 143 | 144 | #### useWebSocketEffect 145 | 146 | Use this for connect to an event in you component 147 | 148 | ```js 149 | const Comp = () => { 150 | const [messages, setMessage] = useState([]); 151 | 152 | WebsocketContext.useWebSocketEffect( 153 | (message) => { 154 | setMessage([...messages, message]); 155 | }, 156 | ); 157 | 158 | return ; 159 | }; 160 | ``` 161 | 162 | ### supports 163 | 164 | | react-signalr | @microsoft/signalr | 165 | | -------------- | ------------------ | 166 | | 0.2.0 - 0.2.18 | 7.x | 167 | | 0.2.19 | 7.x - 8.x | 168 | 169 | ### React-Native 170 | 171 | Full supported 172 | -------------------------------------------------------------------------------- /src/webSocket/provider/index.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useEffect, useRef, useState } from "react"; 3 | import jsCookie from "js-cookie"; 4 | import { createConnection, isConnectionConnecting } from "../utils"; 5 | import { ProviderProps } from "./types"; 6 | import { Context } from "../types"; 7 | import { useEvent, __DEV__ } from "../../utils"; 8 | 9 | const IS_SIGNAL_R_CONNECTED = "IS_SIGNAL_R_CONNECTED"; 10 | const KEY_LAST_CONNECTION_TIME = "KEY_LAST_CONNECTION_TIME"; 11 | 12 | function providerFactory(context: Context) { 13 | const Provider = ({ 14 | url, 15 | connectEnabled = true, 16 | children, 17 | dependencies = [], 18 | onError, 19 | onOpen, 20 | onClose, 21 | logger = __DEV__ ? console : null, 22 | }: ProviderProps) => { 23 | const onErrorRef = useEvent(onError); 24 | // eslint-disable-next-line @typescript-eslint/no-empty-function 25 | const clear = useRef(() => {}); 26 | 27 | function refreshConnection() { 28 | if (!connectEnabled) { 29 | return; 30 | } 31 | 32 | let lastConnectionSentState: number | null = 33 | Number(jsCookie.get(KEY_LAST_CONNECTION_TIME)) || null; 34 | let anotherTabConnectionId: string | null = null; 35 | 36 | /** If another tab connected to signalR we will receive this event */ 37 | hermes.on(IS_SIGNAL_R_CONNECTED, (_anotherTabConnectionId) => { 38 | // connected tab will send empty _anotherTabConnectionId before close 39 | if (!_anotherTabConnectionId) { 40 | lastConnectionSentState = null; 41 | anotherTabConnectionId = null; 42 | checkForStart(); 43 | 44 | return; 45 | } 46 | 47 | logger?.log("_anotherTabConnectionId"); 48 | 49 | anotherTabConnectionId = _anotherTabConnectionId; 50 | lastConnectionSentState = Date.now(); 51 | if (!isConnectionConnecting(context.connection)) { 52 | sentInterval && clearInterval(sentInterval); 53 | context.connection?.close(); 54 | } 55 | }); 56 | 57 | let sentInterval: any; 58 | 59 | async function checkForStart() { 60 | function syncWithTabs() { 61 | if (anotherTabConnectionId) { 62 | clearInterval(sentInterval); 63 | context.connection?.close(); 64 | 65 | return; 66 | } 67 | 68 | shoutConnected(String(context.key)); 69 | } 70 | logger?.log({ 71 | lastConnectionSentState, 72 | isConnectionConnecting: isConnectionConnecting(context.connection), 73 | }); 74 | 75 | if ( 76 | (!lastConnectionSentState || 77 | lastConnectionSentState < Date.now() - 5000) && 78 | !isConnectionConnecting(context.connection) 79 | ) { 80 | try { 81 | createConnection(context, { 82 | onClose, 83 | onOpen, 84 | logger, 85 | url, 86 | onErrorRef, 87 | }); 88 | 89 | shoutConnected(String(context.key)); 90 | 91 | sentInterval = setInterval(syncWithTabs, 4000); 92 | 93 | syncWithTabs(); 94 | } catch (err) { 95 | logger?.error((err as Error).message); 96 | sentInterval && clearInterval(sentInterval); 97 | onErrorRef?.(err as Error); 98 | } 99 | } 100 | } 101 | 102 | checkForStart(); 103 | 104 | const checkInterval = setInterval(checkForStart, 6000); 105 | 106 | /** 107 | * Before of this tab close this event will sent an empty 108 | * anotherTabConnectionId to other tabs 109 | */ 110 | function onBeforeunload() { 111 | if (isConnectionConnecting(context.connection)) { 112 | shoutConnected(null); 113 | clearInterval(sentInterval); 114 | context.connection?.close(); 115 | } 116 | } 117 | 118 | /** AddEventListener is not exist in react-native */ 119 | window?.addEventListener?.("beforeunload", onBeforeunload); 120 | 121 | clear.current = () => { 122 | clearInterval(checkInterval); 123 | sentInterval && clearInterval(sentInterval); 124 | context.connection?.close(); 125 | hermes.off(IS_SIGNAL_R_CONNECTED); 126 | /** RemoveEventListener is not exist in react-native */ 127 | window?.removeEventListener?.("beforeunload", onBeforeunload); 128 | }; 129 | } 130 | 131 | useState(() => { 132 | refreshConnection(); 133 | }); 134 | 135 | const isMounted = useRef(false); 136 | 137 | useEffect(() => { 138 | if (isMounted.current) { 139 | refreshConnection(); 140 | } 141 | 142 | isMounted.current = true; 143 | return () => { 144 | clear.current(); 145 | }; 146 | // eslint-disable-next-line react-hooks/exhaustive-deps 147 | }, [connectEnabled, url, ...dependencies]); 148 | 149 | return children as JSX.Element; 150 | }; 151 | 152 | return Provider; 153 | } 154 | 155 | function shoutConnected(anotherTabConnectionId: string | null) { 156 | if (!anotherTabConnectionId) { 157 | hermes.send(IS_SIGNAL_R_CONNECTED, ""); 158 | jsCookie.set(KEY_LAST_CONNECTION_TIME, ""); 159 | 160 | return; 161 | } 162 | 163 | hermes.send(IS_SIGNAL_R_CONNECTED, anotherTabConnectionId); 164 | const expires = new Date(); 165 | expires.setSeconds(expires.getSeconds() + 10); 166 | jsCookie.set(KEY_LAST_CONNECTION_TIME, Date.now().toString(), { 167 | expires, 168 | path: "/", 169 | }); 170 | } 171 | 172 | export { providerFactory }; 173 | export type { ProviderProps }; 174 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | /* Basic Options */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | "target": "ES2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 7 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 8 | "lib": [ 9 | "dom", 10 | "ES2020" 11 | ] /* Specify library files to be included in the compilation. */, 12 | // "allowJs": true, /* Allow javascript files to be compiled. */ 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 15 | "declaration": true /* Generates corresponding '.d.ts' file. */, 16 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 17 | "sourceMap": true /* Generates corresponding '.map' file. */, 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "./lib" /* Redirect output structure to the directory. */, 20 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 21 | // "composite": true, /* Enable project compilation */ 22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 23 | // "removeComments": true, /* Do not emit comments to output. */ 24 | // "noEmit": true, /* Do not emit outputs. */ 25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | "skipLibCheck": true, 29 | /* Strict Type-Checking Options */ 30 | "strict": true /* Enable all strict type-checking options. */, 31 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 32 | // "strictNullChecks": true, /* Enable strict null checks. */ 33 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 34 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 35 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | /* Source Map Options */ 55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "exclude": ["example"] 64 | } 65 | -------------------------------------------------------------------------------- /src/socket/provider/index.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useEffect, useRef, useState } from "react"; 3 | import jsCookie from "js-cookie"; 4 | import { createConnection, isConnectionConnecting } from "../utils"; 5 | import { ProviderProps } from "./types"; 6 | import { Context, Hub } from "../types"; 7 | import { useEvent, __DEV__ } from "../../utils"; 8 | 9 | const IS_SOCKET_CONNECTED = "IS_SOCKET_CONNECTED"; 10 | const KEY_LAST_CONNECTION_TIME = "KEY_LAST_CONNECTION_TIME"; 11 | 12 | function providerFactory(Context: Context) { 13 | const Provider = ({ 14 | url, 15 | connectEnabled = true, 16 | automaticReconnect = true, 17 | children, 18 | dependencies = [], 19 | accessTokenFactory, 20 | onError, 21 | ...rest 22 | }: ProviderProps) => { 23 | const onErrorRef = useEvent(onError); 24 | const accessTokenFactoryRef = useEvent(accessTokenFactory); 25 | // eslint-disable-next-line @typescript-eslint/no-empty-function 26 | const clear = useRef(() => {}); 27 | 28 | function refreshConnection() { 29 | if (!connectEnabled) { 30 | return; 31 | } 32 | 33 | const connection = createConnection(url, { 34 | autoConnect: true, 35 | reconnection: automaticReconnect, 36 | transportOptions: { 37 | polling: { 38 | extraHeaders: { 39 | Authorization: () => accessTokenFactoryRef?.() || "", 40 | }, 41 | }, 42 | }, 43 | ...rest, 44 | }); 45 | 46 | Context.connection = connection; 47 | 48 | //@ts-ignore 49 | Context.reOn(); 50 | 51 | let lastConnectionSentState: number | null = 52 | Number(jsCookie.get(KEY_LAST_CONNECTION_TIME)) || null; 53 | let anotherTabConnectionId: string | null = null; 54 | 55 | /** If another tab connected to signalR we will receive this event */ 56 | hermes.on(IS_SOCKET_CONNECTED, (_anotherTabConnectionId) => { 57 | // connected tab will send empty _anotherTabConnectionId before close 58 | if (!_anotherTabConnectionId) { 59 | lastConnectionSentState = null; 60 | anotherTabConnectionId = null; 61 | checkForStart(); 62 | 63 | return; 64 | } 65 | if (__DEV__) { 66 | console.log("Another tab connected"); 67 | } 68 | anotherTabConnectionId = _anotherTabConnectionId; 69 | lastConnectionSentState = Date.now(); 70 | if (!isConnectionConnecting(connection)) { 71 | sentInterval && clearInterval(sentInterval); 72 | connection.close(); 73 | } 74 | }); 75 | 76 | let sentInterval: any; 77 | 78 | async function checkForStart() { 79 | function syncWithTabs() { 80 | if (anotherTabConnectionId) { 81 | clearInterval(sentInterval); 82 | connection.close(); 83 | 84 | return; 85 | } 86 | 87 | shoutConnected(connection.id); 88 | } 89 | if ( 90 | (!lastConnectionSentState || 91 | lastConnectionSentState < Date.now() - 5000) && 92 | !isConnectionConnecting(connection) 93 | ) { 94 | try { 95 | shoutConnected(connection.id); 96 | connection.open(); 97 | 98 | sentInterval = setInterval(syncWithTabs, 4000); 99 | 100 | syncWithTabs(); 101 | } catch (err) { 102 | console.log(err); 103 | sentInterval && clearInterval(sentInterval); 104 | onErrorRef?.(err as Error); 105 | } 106 | } 107 | } 108 | 109 | checkForStart(); 110 | 111 | const checkInterval = setInterval(checkForStart, 6000); 112 | 113 | /** 114 | * Before of this tab close this event will sent an empty 115 | * anotherTabConnectionId to other tabs 116 | */ 117 | function onBeforeunload() { 118 | if (isConnectionConnecting(connection)) { 119 | shoutConnected(null); 120 | clearInterval(sentInterval); 121 | connection.disconnect(); 122 | return; 123 | } 124 | } 125 | 126 | /** AddEventListener is not exist in react-native */ 127 | window?.addEventListener?.("beforeunload", onBeforeunload); 128 | 129 | clear.current = () => { 130 | clearInterval(checkInterval); 131 | sentInterval && clearInterval(sentInterval); 132 | connection.disconnect(); 133 | hermes.off(IS_SOCKET_CONNECTED); 134 | /** RemoveEventListener is not exist in react-native */ 135 | window?.removeEventListener?.("beforeunload", onBeforeunload); 136 | }; 137 | } 138 | 139 | useState(() => { 140 | refreshConnection(); 141 | }); 142 | 143 | const isMounted = useRef(false); 144 | 145 | useEffect(() => { 146 | if (isMounted.current) { 147 | refreshConnection(); 148 | } 149 | 150 | isMounted.current = true; 151 | return () => { 152 | clear.current(); 153 | }; 154 | // eslint-disable-next-line react-hooks/exhaustive-deps 155 | }, [connectEnabled, url, ...dependencies]); 156 | 157 | return children as JSX.Element; 158 | }; 159 | 160 | return Provider; 161 | } 162 | 163 | function shoutConnected(anotherTabConnectionId: string | null) { 164 | if (!anotherTabConnectionId) { 165 | hermes.send(IS_SOCKET_CONNECTED, ""); 166 | jsCookie.set(KEY_LAST_CONNECTION_TIME, ""); 167 | 168 | return; 169 | } 170 | 171 | hermes.send(IS_SOCKET_CONNECTED, anotherTabConnectionId); 172 | const expires = new Date(); 173 | expires.setSeconds(expires.getSeconds() + 10); 174 | jsCookie.set(KEY_LAST_CONNECTION_TIME, Date.now().toString(), { 175 | expires, 176 | path: "/", 177 | }); 178 | } 179 | 180 | export { providerFactory }; 181 | export type { ProviderProps }; 182 | -------------------------------------------------------------------------------- /src/signalr/provider/index.ts: -------------------------------------------------------------------------------- 1 | import hermes from "hermes-channel"; 2 | import { useEffect, useRef, useState } from "react"; 3 | import jsCookie from "js-cookie"; 4 | import { createConnection, isConnectionConnecting } from "../utils"; 5 | import { ProviderProps } from "./types"; 6 | import { Context, Hub } from "../types"; 7 | import { useEvent } from "../../utils"; 8 | 9 | const IS_SIGNAL_R_CONNECTED = "IS_SIGNAL_R_CONNECTED"; 10 | const KEY_LAST_CONNECTION_TIME = "KEY_LAST_CONNECTION_TIME"; 11 | 12 | function providerFactory(Context: Context) { 13 | const Provider = ({ 14 | url, 15 | connectEnabled = true, 16 | automaticReconnect = true, 17 | children, 18 | dependencies = [], 19 | accessTokenFactory, 20 | onError, 21 | onOpen, 22 | onReconnect, 23 | onClosed, 24 | onBeforeClose, 25 | logger, 26 | ...rest 27 | }: ProviderProps) => { 28 | const onErrorRef = useEvent(onError); 29 | const accessTokenFactoryRef = useEvent(accessTokenFactory); 30 | // eslint-disable-next-line @typescript-eslint/no-empty-function 31 | const clear = useRef(() => {}); 32 | 33 | function refreshConnection() { 34 | if (!connectEnabled) { 35 | return; 36 | } 37 | 38 | const connection = createConnection( 39 | url, 40 | { 41 | accessTokenFactory: () => accessTokenFactoryRef?.() || "", 42 | logger, 43 | ...rest, 44 | }, 45 | automaticReconnect, 46 | ); 47 | 48 | connection.onreconnecting((error) => onErrorRef?.(error)); 49 | connection.onreconnected(() => onReconnect?.(connection)); 50 | 51 | Context.connection = connection; 52 | 53 | //@ts-ignore 54 | Context.reOn(); 55 | 56 | connection.onclose((error) => { 57 | onClosed?.(error); 58 | }); 59 | 60 | let lastConnectionSentState: number | null = 61 | Number(jsCookie.get(KEY_LAST_CONNECTION_TIME)) || null; 62 | let anotherTabConnectionId: string | null = null; 63 | 64 | /** If another tab connected to signalR we will receive this event */ 65 | hermes.on(IS_SIGNAL_R_CONNECTED, (_anotherTabConnectionId) => { 66 | // connected tab will send empty _anotherTabConnectionId before close 67 | if (!_anotherTabConnectionId) { 68 | lastConnectionSentState = null; 69 | anotherTabConnectionId = null; 70 | checkForStart(); 71 | 72 | return; 73 | } 74 | if (logger) { 75 | console.log("Another tab connected"); 76 | } 77 | anotherTabConnectionId = _anotherTabConnectionId; 78 | lastConnectionSentState = Date.now(); 79 | if (!isConnectionConnecting(connection)) { 80 | sentInterval && clearInterval(sentInterval); 81 | connection.stop(); 82 | } 83 | }); 84 | 85 | let sentInterval: any; 86 | 87 | async function checkForStart() { 88 | function syncWithTabs() { 89 | if (anotherTabConnectionId) { 90 | clearInterval(sentInterval); 91 | connection.stop(); 92 | 93 | return; 94 | } 95 | 96 | shoutConnected(connection.connectionId); 97 | } 98 | 99 | if ( 100 | (!lastConnectionSentState || 101 | lastConnectionSentState < Date.now() - 5000) && 102 | !isConnectionConnecting(connection) 103 | ) { 104 | try { 105 | shoutConnected(connection.connectionId); 106 | await connection.start(); 107 | onOpen?.(connection); 108 | 109 | sentInterval = setInterval(syncWithTabs, 4000); 110 | 111 | syncWithTabs(); 112 | } catch (err) { 113 | console.log(err); 114 | sentInterval && clearInterval(sentInterval); 115 | onErrorRef?.(err as Error); 116 | } 117 | } 118 | } 119 | 120 | checkForStart(); 121 | 122 | const checkInterval = setInterval(checkForStart, 6000); 123 | 124 | /** 125 | * Before of this tab close this event will sent an empty 126 | * anotherTabConnectionId to other tabs 127 | */ 128 | function onBeforeunload() { 129 | if (isConnectionConnecting(connection)) { 130 | shoutConnected(null); 131 | clearInterval(sentInterval); 132 | connection.stop(); 133 | } 134 | } 135 | 136 | /** AddEventListener is not exist in react-native */ 137 | window?.addEventListener?.("beforeunload", onBeforeunload); 138 | 139 | clear.current = async () => { 140 | clearInterval(checkInterval); 141 | sentInterval && clearInterval(sentInterval); 142 | await onBeforeClose?.(connection); 143 | 144 | connection.stop(); 145 | hermes.off(IS_SIGNAL_R_CONNECTED); 146 | /** RemoveEventListener is not exist in react-native */ 147 | window?.removeEventListener?.("beforeunload", onBeforeunload); 148 | }; 149 | } 150 | 151 | useState(() => { 152 | refreshConnection(); 153 | }); 154 | 155 | const isMounted = useRef(false); 156 | 157 | useEffect(() => { 158 | if (isMounted.current) { 159 | refreshConnection(); 160 | } 161 | 162 | isMounted.current = true; 163 | return () => { 164 | clear.current(); 165 | }; 166 | // eslint-disable-next-line react-hooks/exhaustive-deps 167 | }, [connectEnabled, url, ...dependencies]); 168 | 169 | return children as JSX.Element; 170 | }; 171 | 172 | return Provider; 173 | } 174 | 175 | function shoutConnected(anotherTabConnectionId: string | null) { 176 | if (!anotherTabConnectionId) { 177 | hermes.send(IS_SIGNAL_R_CONNECTED, ""); 178 | jsCookie.set(KEY_LAST_CONNECTION_TIME, ""); 179 | 180 | return; 181 | } 182 | 183 | hermes.send(IS_SIGNAL_R_CONNECTED, anotherTabConnectionId); 184 | const expires = new Date(); 185 | expires.setSeconds(expires.getSeconds() + 10); 186 | jsCookie.set(KEY_LAST_CONNECTION_TIME, Date.now().toString(), { 187 | expires, 188 | path: "/", 189 | }); 190 | } 191 | 192 | export { providerFactory }; 193 | export type { ProviderProps }; 194 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.2.24](https://github.com/hosseinmd/react-signalr/compare/v0.2.23...v0.2.24) (2024-09-28) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * remove useless random-value package ([e5f3803](https://github.com/hosseinmd/react-signalr/commit/e5f38033ba8396d7d8ab2317e3f374c8b0b5383d)) 11 | 12 | ### [0.2.23](https://github.com/hosseinmd/react-signalr/compare/v0.2.22...v0.2.23) (2024-09-11) 13 | 14 | 15 | ### Features 16 | 17 | * support react-native headers for websocket ([9048654](https://github.com/hosseinmd/react-signalr/commit/9048654b73f751472083f4c24406ee3e8f07b99a)) 18 | 19 | ### [0.2.22](https://github.com/hosseinmd/react-signalr/compare/v0.2.21...v0.2.22) (2024-05-26) 20 | 21 | 22 | ### Features 23 | 24 | * add retyrDelay to automaticReconnect ([93b43b1](https://github.com/hosseinmd/react-signalr/commit/93b43b1fab2860da85702ed2723d8ec113561015)) 25 | 26 | ### [0.2.21](https://github.com/hosseinmd/react-signalr/compare/v0.2.20...v0.2.21) (2024-05-26) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * source map missed ([3f360fa](https://github.com/hosseinmd/react-signalr/commit/3f360fa2c4a2972ec0fa8c3787962a465add1b82)) 32 | 33 | ### [0.2.20](https://github.com/hosseinmd/react-signalr/compare/v0.2.19...v0.2.20) (2024-05-26) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * hub type ([24b1ef9](https://github.com/hosseinmd/react-signalr/commit/24b1ef90d4cebfdf3e971422ce0ed2dc95a82e11)) 39 | 40 | ### [0.2.19](https://github.com/hosseinmd/react-signalr/compare/v0.2.18...v0.2.19) (2024-01-29) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * update signalR ([3529250](https://github.com/hosseinmd/react-signalr/commit/35292507abd12ca605ee45b8c11467761e0a8399)) 46 | 47 | ### [0.2.18](https://github.com/hosseinmd/react-signalr/compare/v0.2.17...v0.2.18) (2023-09-08) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * add separate files for each tools ([44a18e5](https://github.com/hosseinmd/react-signalr/commit/44a18e549da5fea5eee644c3609b7669cbaac8e6)) 53 | * example ([5b6029e](https://github.com/hosseinmd/react-signalr/commit/5b6029eedbedd060d497e88a7aab1e0dd0a7cfc4)) 54 | * invoke when share connection is disabled ([dde6bfc](https://github.com/hosseinmd/react-signalr/commit/dde6bfc49a8f7e5dc25eda218b1753ed261cdfed)) 55 | * remove deps of events ([b842ada](https://github.com/hosseinmd/react-signalr/commit/b842ada44b30b42f79116b633ec1e55ab1ce7a53)) 56 | * signalr example ([39cf498](https://github.com/hosseinmd/react-signalr/commit/39cf49845da79f1e164aa5b70459c4a23d3c821f)) 57 | 58 | ### [0.2.17](https://github.com/hosseinmd/react-signalr/compare/v0.2.16...v0.2.17) (2023-06-25) 59 | 60 | 61 | ### Features 62 | 63 | * add automatic reconnect feature ([#26](https://github.com/hosseinmd/react-signalr/issues/26)) ([4f8d330](https://github.com/hosseinmd/react-signalr/commit/4f8d3307a0c43a91a8f842739f4b062032fa36af)) 64 | 65 | ### [0.2.16](https://github.com/hosseinmd/react-signalr/compare/v0.2.11...v0.2.16) (2023-05-20) 66 | 67 | ### Bug Fixes 68 | 69 | - export logger ([83c853e](https://github.com/hosseinmd/react-signalr/commit/83c853ea18a131adde399a0f0c97a92eafb2626d)), closes [/github.com/hosseinmd/react-signalr/issues/10#issuecomment-1192870945](https://github.com/hosseinmd//github.com/hosseinmd/react-signalr/issues/10/issues/issuecomment-1192870945) 70 | - provider children type ([fa41a26](https://github.com/hosseinmd/react-signalr/commit/fa41a26b4a23091e07e19b9489f841c160d8d7e5)) 71 | - source map warning ([6183052](https://github.com/hosseinmd/react-signalr/commit/6183052ac933bf00259c6d7ba01afd8e999a8d1e)) 72 | 73 | ### [0.2.15](https://github.com/hosseinmd/react-signalr/compare/v0.2.13...v0.2.15) (2023-05-20) 74 | 75 | ### Bug Fixes 76 | 77 | - source map warning ([6183052](https://github.com/hosseinmd/react-signalr/commit/6183052ac933bf00259c6d7ba01afd8e999a8d1e)) 78 | 79 | ### [0.2.13](https://github.com/hosseinmd/react-signalr/compare/v0.2.12...v0.2.13) (2022-07-23) 80 | 81 | ### Bug Fixes 82 | 83 | - provider children type ([fa41a26](https://github.com/hosseinmd/react-signalr/commit/fa41a26b4a23091e07e19b9489f841c160d8d7e5)) 84 | 85 | ### [0.2.12](https://github.com/hosseinmd/react-signalr/compare/v0.2.11...v0.2.12) (2022-07-23) 86 | 87 | ### Bug Fixes 88 | 89 | - export logger ([83c853e](https://github.com/hosseinmd/react-signalr/commit/83c853ea18a131adde399a0f0c97a92eafb2626d)), closes [/github.com/hosseinmd/react-signalr/issues/10#issuecomment-1192870945](https://github.com/hosseinmd//github.com/hosseinmd/react-signalr/issues/10/issues/issuecomment-1192870945) 90 | 91 | ### [0.2.11](https://github.com/hosseinmd/react-signalr/compare/v0.2.10...v0.2.11) (2022-04-19) 92 | 93 | ### Features 94 | 95 | - add onReconnect event to signalr ([763053f](https://github.com/hosseinmd/react-signalr/commit/763053f5810b8d852235eb6c8e43edbabd7367a4)) 96 | 97 | ### [0.2.10](https://github.com/hosseinmd/react-signalr/compare/v0.2.9...v0.2.10) (2022-03-01) 98 | 99 | ### Bug Fixes 100 | 101 | - update signalr version ([443b310](https://github.com/hosseinmd/react-signalr/commit/443b3103a83322caaeece084b1c2dfed57e269d0)) 102 | 103 | ### [0.2.9](https://github.com/hosseinmd/react-signalr/compare/v0.2.8...v0.2.9) (2022-03-01) 104 | 105 | ### Features 106 | 107 | - add onOpen, onClosed and onBeforeClose events to signalr provider ([67ad747](https://github.com/hosseinmd/react-signalr/commit/67ad747f38a87078d680ed2b694be6eab1a776dd)) 108 | 109 | ### [0.2.8](https://github.com/hosseinmd/react-signalr/compare/v0.2.7...v0.2.8) (2022-02-20) 110 | 111 | ### Bug Fixes 112 | 113 | - react-native providerFactory export ([4b78c25](https://github.com/hosseinmd/react-signalr/commit/4b78c25b30fe8ec29fe5a2440628dd0ef9d437c5)) 114 | 115 | ### [0.2.7](https://github.com/hosseinmd/react-signalr/compare/v0.2.6...v0.2.7) (2022-01-30) 116 | 117 | ### Bug Fixes 118 | 119 | - server side rendering ([1d9ed5d](https://github.com/hosseinmd/react-signalr/commit/1d9ed5da2d060b6ca70a84a86cd2bea8b3137c0f)) 120 | 121 | ### [0.2.5](https://github.com/hosseinmd/react-signalr/compare/v0.2.4...v0.2.5) (2022-01-27) 122 | 123 | ### Features 124 | 125 | - implement webSocket ([00c31a8](https://github.com/hosseinmd/react-signalr/commit/00c31a8152fe3513a07fe1a6d6a9ed1e64a61455)) 126 | 127 | ### [0.2.3](https://github.com/hosseinmd/react-signalr/compare/v0.2.2...v0.2.3) (2021-12-28) 128 | 129 | ### Bug Fixes 130 | 131 | - update hermes-channel ([5fe891b](https://github.com/hosseinmd/react-signalr/commit/5fe891b2dce33253537e5892d91e3c3c703361d0)) 132 | 133 | ### [0.2.2](https://github.com/hosseinmd/react-signalr/compare/v0.2.1...v0.2.2) (2021-12-22) 134 | 135 | ### Bug Fixes 136 | 137 | - add uuid react-native-get-random-values ([5cda491](https://github.com/hosseinmd/react-signalr/commit/5cda491650ec93215a972f2a7f36c916a69ab601)) 138 | 139 | ### [0.2.1](https://github.com/hosseinmd/react-signalr/compare/v0.1.7...v0.2.1) (2021-12-22) 140 | 141 | ### Features 142 | 143 | - disable Connection Between tabs (create connections for all tabs) ([#5](https://github.com/hosseinmd/react-signalr/issues/5)) ([265b167](https://github.com/hosseinmd/react-signalr/commit/265b1676484b1d3e84b052bf5eec29571456c938)) 144 | 145 | ### Bug Fixes 146 | 147 | - update hermes-channel ([ad9f214](https://github.com/hosseinmd/react-signalr/commit/ad9f2149b4e5b08148f96e69838eeed42ff187f4)) 148 | 149 | ### [0.2.0](https://github.com/hosseinmd/react-signalr/compare/v0.1.7...v0.1.8) (2021-12-07) 150 | 151 | ### Features 152 | 153 | - disable Connection Between tabs (create connections for all tabs) ([#5](https://github.com/hosseinmd/react-signalr/issues/5)) ([265b167](https://github.com/hosseinmd/react-signalr/commit/265b1676484b1d3e84b052bf5eec29571456c938)) 154 | 155 | ### [0.1.6](https://github.com/hosseinmd/react-signalr/compare/v0.1.5...v0.1.6) (2021-10-01) 156 | 157 | ### Bug Fixes 158 | 159 | - socket args ([5948572](https://github.com/hosseinmd/react-signalr/commit/5948572f4ab317e0d0b3f3089ebaa538551fe333)) 160 | - on to reconnected ([b4a9a4c](https://github.com/hosseinmd/react-signalr/commit/b4a9a4c5affab7fa8da702c2a7268fc0892bef1a)) 161 | - reconnection listen ([b683f41](https://github.com/hosseinmd/react-signalr/commit/b683f41d16c38094df98e3fe57d0edb8a689898f)) 162 | - socket provider types ([1da8950](https://github.com/hosseinmd/react-signalr/commit/1da8950755d6f108b36873c3292f99f3aa0f8348)) 163 | 164 | ### [0.1.2](https://github.com/hosseinmd/react-signalr/compare/v0.1.1...v0.1.2) (2021-09-30) 165 | 166 | ### Features 167 | 168 | - implements socket.io ([#4](https://github.com/hosseinmd/react-signalr/issues/4)) ([c2dbd70](https://github.com/hosseinmd/react-signalr/commit/c2dbd704bc24a13a72141421cd29485ea897ac95)) 169 | 170 | ### Bug Fixes 171 | 172 | - few missed socket context ([eb6989b](https://github.com/hosseinmd/react-signalr/commit/eb6989ba34b0ff0e8bd0072f18713fffd5eb66c8)) 173 | 174 | ### [0.1.1](https://github.com/hosseinmd/react-signalr/compare/v0.1.0...v0.1.1) (2021-07-03) 175 | 176 | ### Bug Fixes 177 | 178 | - get unlimited arguments ([5fa0168](https://github.com/hosseinmd/react-signalr/commit/5fa0168d2006c6deb22d22176529fc034c8298b1)) 179 | 180 | ### [0.1.0](https://github.com/hosseinmd/react-signalr/compare/v0.0.10...v0.0.11) (2021-06-02) 181 | 182 | ### Features 183 | 184 | - handle dynamic events ([0dae25c](https://github.com/hosseinmd/react-signalr/commit/0dae25c6323899f70abe39ed7946bb037567a2c0)) 185 | 186 | ### [0.0.10](https://github.com/hosseinmd/react-signalr/compare/v0.0.9...v0.0.10) (2020-12-30) 187 | 188 | ### Features 189 | 190 | - implement invoke ([5adf138](https://github.com/hosseinmd/react-signalr/commit/5adf138866591002e5e91d983d878840b6f62c41)) 191 | 192 | ### Bug Fixes 193 | 194 | - types of callbacks and invoke ([a532848](https://github.com/hosseinmd/react-signalr/commit/a532848d36e792a8f2f69c126e2656adf9bee62c)) 195 | 196 | ### [0.0.9](https://github.com/hosseinmd/react-signalr/compare/v0.0.7...v0.0.9) (2020-12-27) 197 | 198 | ### Features 199 | 200 | - add onError to provider ([9f5cf5f](https://github.com/hosseinmd/react-signalr/commit/9f5cf5f09f43de7506d1905b388ea2ddcaa8e0ab)) 201 | 202 | ### Bug Fixes 203 | 204 | - split react-native provider ([34500f3](https://github.com/hosseinmd/react-signalr/commit/34500f31397456108b1c5105697731ad037cedd9)) 205 | 206 | ### [0.0.7](https://github.com/hosseinmd/react-signalr/compare/v0.0.6...v0.0.7) (2020-12-16) 207 | 208 | ### Bug Fixes 209 | 210 | - don't reconnect if another tab connected ([9e341eb](https://github.com/hosseinmd/react-signalr/commit/9e341eb3a24f566944e51f632ca2f744c926fca6)) 211 | 212 | ### [0.0.6](https://github.com/hosseinmd/react-signalr/compare/v0.0.5...v0.0.6) (2020-12-16) 213 | 214 | ### [0.0.5](https://github.com/hosseinmd/react-signalr/compare/v0.0.4...v0.0.5) (2020-12-14) 215 | 216 | ### [0.0.4](https://github.com/hosseinmd/react-signalr/compare/v0.0.3...v0.0.4) (2020-12-14) 217 | 218 | ### [0.0.3](https://github.com/hosseinmd/react-signalr/compare/v0.0.2...v0.0.3) (2020-12-14) 219 | 220 | ### Bug Fixes 221 | 222 | - useSignalREffect types ([fa4a327](https://github.com/hosseinmd/react-signalr/commit/fa4a327527ae8095d9a2151f3f704eee60d2b10e)) 223 | 224 | ### [0.0.2](https://github.com/hosseinmd/react-signalr/compare/v0.0.1...v0.0.2) (2020-12-14) 225 | 226 | ### Bug Fixes 227 | 228 | - type ([27ee4e1](https://github.com/hosseinmd/react-signalr/commit/27ee4e1ae54597ac3e9bb7120bb318441980480a)) 229 | 230 | ### 0.0.1 (2020-12-14) 231 | --------------------------------------------------------------------------------