{"id":38910918,"url":"https://github.com/papablack/rws-client","last_synced_at":"2026-01-17T15:18:28.518Z","repository":{"id":207389009,"uuid":"719114588","full_name":"papablack/rws-client","owner":"papablack","description":"Client for RWS","archived":false,"fork":false,"pushed_at":"2024-06-25T18:55:14.000Z","size":3099,"stargazers_count":1,"open_issues_count":3,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-07-01T17:23:50.778Z","etag":null,"topics":["framework","frontend","fullstack","typescript","typescript-library"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/rws-js-client","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/papablack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-15T13:39:35.000Z","updated_at":"2024-07-16T12:40:30.851Z","dependencies_parsed_at":"2023-11-30T17:56:02.770Z","dependency_job_id":"5b5d0b0d-fc0a-4740-8e6e-ca704fec790e","html_url":"https://github.com/papablack/rws-client","commit_stats":null,"previous_names":["papablack/rws-client"],"tags_count":172,"template":false,"template_full_name":null,"purl":"pkg:github/papablack/rws-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papablack%2Frws-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papablack%2Frws-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papablack%2Frws-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papablack%2Frws-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/papablack","download_url":"https://codeload.github.com/papablack/rws-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papablack%2Frws-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28511213,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","frontend","fullstack","typescript","typescript-library"],"created_at":"2026-01-17T15:18:27.710Z","updated_at":"2026-01-17T15:18:28.503Z","avatar_url":"https://github.com/papablack.png","language":"JavaScript","readme":"# Realtime Web Suit client setup and configuration guide\r\n\r\nRealtime Web Suit is a web-component powered, MS FAST powered fullstack-oriented framework that you can use to create domain-agnostic modular asynchoronous components with intershared authorized states.\r\n\r\n## Table of Contents\r\n\r\n1. [Overview](#overview)\r\n2. [Getting Started](#getting-started)\r\n3. [Key Components: RWSClient \u0026 RoutingService](#key-components-rwsclient--routingservice)\r\n4. [Component Initialization](#component-initialization)\r\n5. [DI](#dependency-injection)\r\n6. [Frontend routes](#frontend-routes)\r\n7. [Backend Imports](#backend-imports)\r\n8. [Utilizing APIService](#utilizing-apiservice)\r\n9. [Notifier](#notifier)\r\n10. [Service Worker](#service-worker)\r\n11. [Example: WebChat Component](#example-webchat-component)\r\n12. [Other configs](#other-configs)\r\n13. [Plugins](#plugin-system)\r\n14. [Links](#links)\r\n\r\n## Overview\r\n\r\nThe RWS Frontend Framework is designed to create dynamic and responsive web applications. It integrates seamlessly with the backend and provides a robust set of tools for developing comprehensive web solutions.\r\n\r\n## Getting Started\r\n\r\nTo get started with the RWS Frontend Framework, ensure you have the necessary environment set up, including Node.js and any other dependencies specific to the framework.\r\n\r\nfrom your project dir do:\r\n\r\n```bash\r\nyarn\r\n```\r\n\r\nInitiate cfg files and webpack build:\r\n```bash\r\nrws-client init\r\n```\r\n\r\nto install once and then to build after preparing components:\r\n\r\n```bash\r\nyarn build\r\n```\r\nor to watch for dev\r\n\r\n```bash\r\nyarn watch\r\n```\r\nor to just start server\r\n```bash\r\nyarn server\r\n```\r\n\r\nthen start engine in the site javascript (can be inline):\r\n\r\n```javascript\r\nwindow.RWS.client.start(CFG); // it is async function\r\n```\r\n\r\n*or for initial setup then start on certain event (example)* \r\n\r\n```javascript\r\nwindow.RWS.client.setup(CFG).then(() =\u003e {     // it is async function\r\n    $.on('loaded', function(data){\r\n        const optionalNewCfg = { backendRoutes: data.backendRoutes };\r\n        window.RWSClient.start(optionalNewCfg).then();\r\n    })    \r\n});\r\n```\r\n\r\n### default config for RWS:\r\n\r\n```javascript\r\nconst _DEFAULT_CONFIG_VARS = {\r\n    //Build configs\r\n    dev: false,\r\n    hot: false,\r\n    report: false,   \r\n    publicDir: './public',\r\n    publicIndex: 'index.html',      \r\n    outputFileName: 'client.rws.js',\r\n    outputDir: process.cwd() + '/build',\r\n    //Frontend RWS client configs  \r\n    backendUrl: null,\r\n    wsUrl: null,\r\n    partedDirUrlPrefix: '/lib/rws',\r\n    partedPrefix: 'rws',\r\n    pubUrlFilePrefix: '/',\r\n    //Universal configs\r\n    transports: ['websocket'],    \r\n    parted: false,        \r\n}\r\n\r\n```\r\n\r\n*The options description:*\r\n\r\n|  **Option**  | **Description** |  **Default**  |\r\n|--------------|-----------------|---------------|\r\n| backendUrl | Url for backend integration (API calls) | null |\r\n| wsUrl | Url for backend integration (Websocket calls) | null |\r\n| backendRoutes | Backend routes object imported from backend node for integration with API calls | null |\r\n| apiPrefix | Prefix for API calls | / |\r\n| routes | Routes for frontend routing | {} |\r\n| transports | Websockets transports method | ['websockets'] |\r\n| user | User object for backend auth / frontend data source | null |\r\n| ignoreRWSComponents | Do not declare base RWS components (uploader, progress) | false |\r\n| pubUrlFilePrefix | the url for accessing files from browser URL | / |\r\n| pubUrl | the url for accessing public dir from browser URL | / |\r\n| outputDir | build dir | ./build |\r\n| outputFileName | output file name | rws.client.js |\r\n| publicDir | public dir for HTML serving | ./public |\r\n| tsConfigPath | tsconfig.json path | ./tsconfig.njson |\r\n| entry | default TS entry for transpilation | ./src/index.ts |\r\n| parted | \"Parted\" mode if enabled. \"Monolith\" if disabled. Parted mode outputs every component as separate js file and asynchronously adds them to browser. Monolith is single file js build. | false |\r\n| partedPrefix | parted file prefix ([prefix].[cmp name].js) | rws |\r\n| partedDirUrlPrefix | URL for generated js parted files directory | / |\r\n| copyAssets | An option for defining structure that will be copied after build | {} |\r\n\r\n*copyAssets example*\r\n\r\n```json\r\n\"copyAssets\": {\r\n    \"./public/js/\": [ // target directory\r\n      \"./build/\", // copy this directory to target\r\n      \"./src/styles/compiled/main.css\" //copy this file to target\r\n    ]\r\n}\r\n```\r\n\r\n### The FRONT config TS interface:\r\n\r\n```typescript\r\ninterface IRWSConfig {\r\n    defaultLayout?: typeof RWSViewComponent\r\n    backendUrl?: string\r\n    wsUrl?: string\r\n    backendRoutes?: any[]\r\n    apiPrefix?: string\r\n    routes?: IFrontRoutes\r\n    transports?: string[]\r\n    user?: any\r\n    ignoreRWSComponents?: boolean\r\n    pubUrl?: string\r\n    pubUrlFilePrefix?: string\r\n    partedDirUrlPrefix?: string\r\n    dontPushToSW?: boolean\r\n    parted?: boolean\r\n    partedFileDir?: string\r\n    partedPrefix?: string\r\n    routing_enabled?: boolean\r\n    _noLoad?: boolean    \r\n}\r\n```\r\n### The FRONT webpack config:\r\n\r\n```javascript\r\nconst path = require('path');\r\n\r\nconst RWSWebpackWrapper  = require('@rws-framework/client/rws.webpack.config');\r\n\r\n\r\nconst executionDir = process.cwd();\r\n\r\nmodule.exports = RWSWebpackWrapper({  \r\n  tsConfigPath: executionDir + '/tsconfig.json',\r\n  entry: `${executionDir}/src/index.ts`,  \r\n  publicDir:  path.resolve(executionDir, 'public'),\r\n  outputDir:  path.resolve(executionDir, 'build'),\r\n  outputFileName: 'jtrainer.client.js'\r\n});\r\n```\r\n\r\n\r\n\r\n## Key Components\r\n\r\n### RWSClient\r\n##\r\n`RWS.client` is the heart of the framework, managing configuration and initialization. It sets up routes, backend connections, and other essential framework services.\r\n\r\n\r\n### RoutingService\r\n\r\n`RoutingService` handles the navigation and routing within your application. It ensures that URL changes reflect the correct component rendering.\r\n\r\n**Depreciation Notice**\r\n\r\n***RoutingService will be moved to @rws-framework/browser-router near future***\r\n\r\n### WSService\r\n\r\n`WSService` handles Websockets messenging to the backend.\r\n\r\n**Depreciation Notice**\r\n***WSService will be moved to @rws-framework/nest-interconnectors in near future***\r\n\r\n### APIService\r\n\r\n`APIService` handles API requests to the backend.\r\n\r\nImplementing the Framework\r\n\r\n**Main File:**\r\n\r\nThe main file (`index.ts`) is where you initialize the RWSClient. Here, you configure your routes, backend routes, and component initializations.\r\n\r\nFollowing is example of full usage of the framework\r\n\r\n```typescript\r\nasync function initializeApp() {           \r\n    const theClient = RWSContainer().get(RWSClient);    \r\n\r\n    theClient.addRoutes(frontendRoutes);\r\n    theClient.setBackendRoutes(backendRoutes());\r\n\r\n    theClient.enableRouting();\r\n    \r\n    theClient.onInit(async () =\u003e {        \r\n\r\n        // For single file output:\r\n        initComponents(theClient.appConfig.get('parted'));  // start components for monolith mode      \r\n        theClient.defineComponents(); // start RWS conponents\r\n\r\n        //custom outside components registering\r\n        provideFASTDesignSystem()\r\n            .register(fastButton())\r\n            .register(fastTab())\r\n            .register(fastSlider())\r\n            .register(fastSelect())\r\n            .register(fastDivider())\r\n            .register(fastMenu())\r\n            .register(fastMenuItem())\r\n        ;\r\n\r\n        // Service worker code\r\n        // const swFilePath: string = `${theClient.appConfig.get('pubUrl')}/service_worker.js`;          \r\n\r\n        // await theClient.swService.registerServiceWorker();        \r\n\r\n        //if(theClient.getUser()){\r\n            // theClient.pushUserToServiceWorker({...theClient.getUser(), instructor: false});  \r\n        //}\r\n\r\n    });\r\n\r\n    theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) =\u003e void, notifierOptions: any = {}) =\u003e {\r\n        switch(uiType){\r\n        case 'notification':\r\n            let notifType = 'success';\r\n\r\n            if(logType === 'error'){\r\n                notifType = 'error';\r\n            }\r\n\r\n            if(logType === 'warning'){\r\n                notifType = 'warning';\r\n            }\r\n\r\n            return alertify.notify(message, notifType, 5, onConfirm);\r\n               \r\n        case 'alert':                \r\n            const alertObj = alertify.alert('Junction AI Notification', message, onConfirm);\r\n\r\n            Object.keys(notifierOptions).forEach(key =\u003e {\r\n                const optionValue = notifierOptions[key];\r\n\r\n                if(key === 'width'){\r\n                    \r\n                    alertObj.elements.dialog.style = `max-width: ${optionValue};`;\r\n                    \r\n                    return;\r\n                }\r\n\r\n                alertObj.set(key, optionValue);\r\n            });\r\n\r\n            alertObj.show();\r\n\r\n            return alertObj;    \r\n        case 'silent':\r\n            if(logType == 'warning'){\r\n                console.warn(message);\r\n            }else if(logType == 'error'){\r\n                console.error(message);\r\n            }else{\r\n                console.log(message);\r\n            }            \r\n            return;    \r\n        }\r\n    });\r\n    \r\n    theClient.assignClientToBrowser();\r\n}\r\n\r\ninitializeApp().catch(console.error);\r\n```\r\n\r\n## Component Initialization\r\n\r\nIn `application/_initComponents.ts`, you initialize the custom components used in your application. If components added in here will include other components they dont need to be listed here. A component imported in this mode needs to be imported once.\r\n\r\n**This should be conditioned not to execute imported code when using parted mode.**\r\n\r\n### Default component structure\r\n\r\n```\r\ncomponent-dir/\r\n    component.ts\r\n    template.html\r\n    styles/\r\n        layout.scss\r\n```\r\n\r\n**WARNING** *All html templates refer to variable \"T\" as to FASTElement templating html scope. It contains all the functions FAST templates uses in html. F.e:* **T.html**, **T.when**, **T.repeat**\r\n\r\n```html\r\n\u003cdiv class=\"convo-area-wrap\"\u003e\r\n    \u003cheader\u003e  \r\n        \u003cdiv class=\"header-inner\"\u003e\u003c/div\u003e      \r\n        ${T.when(x =\u003e x.noChoose === 'false',  (item, index) =\u003e T.html`\u003cdiv\u003e\r\n            \u003cchat-convo-models :chosenModel=\"${x =\u003e x.chosenModel}\"\u003e\u003c/chat-convo-models\u003e\r\n        \u003c/div\u003e`)}\r\n        \u003cdiv\u003e\r\n            \u003ch2\u003e${ x =\u003e x.chatContext ? x.chatContext.label : 'loading...' }\u003c/h2\u003e\r\n            \u003ch3\u003e\u003cstrong\u003e${ x =\u003e x.messageList.length }\u003c/strong\u003e messages in total\u003c/h3\u003e\r\n        \u003c/div\u003e   \r\n        \u003cfast-divider\u003e\u003c/fast-divider\u003e             \r\n    \u003c/header\u003e\r\n    \u003csection\u003e\r\n        \u003cdiv class=\"scroll-area\"\u003e\r\n            \u003cdiv class=\"scroll-content\"\u003e\r\n                ${T.repeat(x =\u003e x.messageList,  (item, index) =\u003e T.html`\r\n                    \u003cchat-convo-message :contentReturn=\"${item =\u003e item}\" :item=\"${item =\u003e item}\"/\u003e\r\n                `)}      \r\n                \r\n                ${T.when(x =\u003e !x.messageList.length,  (item, index) =\u003e T.html`\r\n                    \u003cp class=\"no-chat\"\u003eNo messages\u003c/p\u003e\r\n                `)}   \r\n            \u003c/div\u003e\r\n        \u003c/div\u003e\r\n    \u003c/section\u003e  \r\n\r\n\u003c/div\u003e\r\n```\r\n\r\n### application/_initComponents.ts\r\n\r\nOnly if parted mode is false.\r\n\r\n```typescript\r\nimport { ChatNav } from '../components/chat-nav/component';\r\nimport { DefaultLayout } from '../components/default-layout/component';\r\nimport { RWSIcon } from '../components/rws-icon/component';\r\nimport { LineSplitter } from '../components/line-splitter/component';\r\nimport { WebChat } from '../components/webchat/component';\r\n\r\nexport default (partedMode: boolean = false) =\u003e {\r\n    if(!partedMode){\r\n        WebChat;\r\n        LineSplitter;\r\n        DefaultLayout;\r\n        ChatNav;\r\n        RWSIcon;        \r\n    }\r\n};\r\n```\r\n\r\n## RWS Decorators\r\n\r\n**Component needs to extend RWSViewComponent and use @RWSView decorator**:\r\n\r\n```typescript\r\nimport { RWSViewComponent,  RWSView, observable, attr } from '@rws-framework/client';\r\n\r\nconst options?: RWSDecoratorOptions;\r\n\r\n@RWSView('tag-name', options)\r\nclass WebChat extends RWSViewComponent {\r\n    @attr tagAttr: string; //HTML tag attr\r\n    @ngAttr fromNgAttr: string; //HTML attr from angular template\r\n    @externalAttr fromExAttr: string; //HTML attr with change observation\r\n    @sanitizedAttr htmlAttr: string; //HTML attr that's sanitized with every val change\r\n    @observable someVar: any; //Var for templates/value change observation\r\n    @externalObservable someExVar: string; //Var for templates/value change observation with external watch\r\n}\r\n```\r\n\r\nThe decorator options type:\r\n\r\n```typescript\r\ninterface RWSDecoratorOptions{\r\n    template?: string, //relative path to HTML template file (default: ./template.html)\r\n    styles?: string //relative path to SCSS file (./styles/layout.scss)\r\n    fastElementOptions?: any //the stuff you would insert into static definition in FASTElement class.\r\n}\r\n\r\n```\r\n\r\n# Dependency Injection\r\n\r\n## Default service usage:\r\n\r\n```typescript\r\nimport { RWSViewComponent, RWSView } from 'rws-js-client';\r\n\r\n@RWSView('your-tag');\r\nclass YourComponent extends RWSViewComponent {\r\n    someMethod(url: string): void\r\n    {\r\n        this.apiService.get(url);\r\n    }\r\n}\r\n \r\n```\r\n\r\nA default service can be used in legacy like this:\r\n\r\n```javascript\r\nwindow.RWS.client.get('ApiService').dateMethodFromRWS();\r\n```\r\n\r\nDefault services: https://github.com/papablack/rws-client/blob/7d16d9c6d83c81c9fe470eb0f507756bc6c71b35/src/components/_component.ts#L58\r\n\r\n## Custom service usage:\r\n\r\n```typescript\r\nimport { \r\n    RWSView, RWSViewComponent, RWSInject,\r\n    DateService, DateServiceInstance\r\n} from 'rws-js-client';\r\n\r\nimport DateService, {DateServiceInstance} from '../../my-custom-services/DateService';\r\n\r\n\r\n@RWSView('your-tag')\r\nclass YourComponent extends RWSViewComponent {\r\n    //usage in props:\r\n    private @RWSInject(ServiceFASTDIPointer) serviceProperty: ServiceClassType; \r\n\r\n    //usage in constructor:\r\n    constructor(\r\n        private @RWSInject(DateService) protected dateService: DateServiceInstance\r\n    ) {\r\n        super();\r\n    }\r\n\r\n    someMethod(url: string): void\r\n    {\r\n        this.dateService.get(url);\r\n    }\r\n}\r\n \r\n```\r\n\r\nCustom service needs to export .getSingleton() as default export and have service class exported as classic export for TS typing:\r\n\r\n```typescript\r\nimport { RWSService } from '@rws-framework/client';\r\n\r\n\r\nclass DateService extends RWSService {\r\n    static _IN_CLIENT: boolean = true //If set engine will let legacy use the service through RWSClient.get method\r\n    //(...)\r\n}\r\n\r\nexport default DateService.getSingleton(); // Fast DI service pointer (it points to instanced service in DI container)\r\nexport { DateService as DateServiceInstance }; // the custom service class type\r\n```\r\n\r\n**Custom service for legacy**\r\n\r\nIf service has static **_IN_CLIENT** set for **true** you can use it like this:\r\n\r\n```javascript\r\nwindow.RWS.client.get('DateService').dateMethodFromRWS();\r\n```\r\n\r\n## Frontend routes\r\n\r\nif you are passing routes this is example routing file for frontend:\r\n\r\n```typescript\r\nexport default {\r\n    '/': renderRouteComponent('Home page', WebChat),    \r\n    '/the/path': renderRouteComponent('Component title', ComponentClassName),   \r\n}\r\n```\r\n\r\nRouter tag:\r\n\r\n```html\r\n    \u003csection\u003e\r\n        \u003crws-router\u003e\u003c/rws-router\u003e\r\n    \u003c/section\u003e\r\n```\r\n\r\n## Backend Imports\r\n\r\n`backendImports.ts` consolidates various backend interfaces, routes, and models, allowing for a synchronized frontend and backend from package https://github.com/papablack/rws\r\n\r\n```typescript\r\nimport IBook from '../../backend/src/models/interfaces/IBook';\r\n\r\nimport { \r\n    IBookInfo,  \r\n  } from '../../backend/src/interfaces/IBookInfo';\r\n\r\nimport backendRoutes from '../../backend/src/routing/routes';\r\n\r\nexport { \r\n    IBook,\r\n    IBookInfo,\r\n    backendRoutes\r\n}\r\n\r\n```\r\n\r\nusage:\r\n\r\n\r\n```typescript\r\n    import { backendRoutes} from '../../backendImport';\r\n\r\n    //index.ts\r\n    const theClient = new RWSClient();\r\n    theClient.setBackendRoutes(backendRoutes());\r\n\r\n```\r\n\r\n\r\n## Utilizing APIService\r\n\r\n`APIService` is used for making HTTP requests to the backend. It simplifies the process of interacting with your API endpoints.\r\n\r\nafter control method we have dynamic types those are: \u003c**ResponseType**, **PayloadType**\u003e\r\n\r\nExample Usage by controller route\r\n\r\n```typescript\r\n  const apiPromise: Promise\u003cITalkApiResponse\u003e = this.apiService.back.post\u003cITalkApiResponse, IApiTalkPayload\u003e('talk:models:prompt', {        \r\n        message: msg,\r\n        model: this.chosenModel,\r\n      });\r\n```\r\n\r\nExample Usage by url\r\n\r\n```typescript\r\n  const apiPromise: Promise\u003cITalkApiResponse\u003e = this.apiService.post\u003cITalkApiResponse, IApiTalkPayload\u003e('/api/path/to/action', {        \r\n        message: msg,\r\n        model: this.chosenModel,\r\n      });\r\n```\r\n\r\n## Notifier\r\n\r\n### Overview\r\n\r\nThe Notifier feature in the RWS Client is a versatile tool for handling notifications within the application. It allows for different types of user interface interactions like alerts, notifications, and silent logging, with varying levels of visibility and user interaction.\r\nUsage\r\n\r\n### Setting the Notifier\r\n\r\n```typescript\r\ntheClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) =\u003e void) =\u003e {\r\n    // Implementation based on uiType\r\n});\r\n\r\n```\r\n\r\nThis function allows setting a custom notifier in the RWS Client. It handles the logic based on `uiType`.\r\n\r\nAlert, Notify, and Silent\r\n\r\n- alert: Displays an alert dialog with the message.\r\n- notify: Shows a notification with the message.\r\n- silent: Silently logs the message to the console.\r\n\r\nEach method can be configured with a `message`, `logType`, and an optional `onConfirm` callback function.\r\n\r\nNote\r\n\r\nEnsure that a notifier is set in the RWS Client to use the `NotifyService` effectively. If no notifier is set, it will default to a warning in the console.\r\n\r\n## Service Worker\r\n\r\nIf you pass ```{serviceWorker: 'service_worker_class_path.ts'}``` to RWS Webpack wrapper function param, the code will build ServiceWorker to pubDir.\r\n\r\nexample ServiceWorker class:\r\n\r\n```typescript\r\nimport SWService, { ServiceWorkerServiceInstance } from '@rws-framework/client/src/services/ServiceWorkerService'\r\nimport {TimeTracker} from '../services/TimeTrackerService';\r\nimport RWSServiceWorker from '@rws-framework/client/src/service_worker/src/_service_worker';\r\nimport { RWSWSService as WSService } from '@rws-framework/client/src/services/WSService'\r\n\r\ndeclare const self: ServiceWorkerGlobalScope;\r\n\r\nclass MyServiceWorker extends RWSServiceWorker {\r\n   public tracker: { currentTracker: TimeTracker | null };    \r\n    public trackersToSync: TimeTracker[];\r\n\r\n    protected regExTypes: { [key: string]: RegExp } = {\r\n        SOME_VIEW: new RegExp('.*:\\\\/\\\\/.*\\\\/#\\\\/([a-z0-9].*)\\\\/route\\\\/action$')\r\n    };\r\n    ignoredUrls = [\r\n        new RegExp('(.*(?=.[^.]*$).*)/#/login'),\r\n        new RegExp('(.*(?=.[^.]*$).*)/#/logout'),\r\n    ];   \r\n\r\n    constructor(){        \r\n        super(self, RWSContainer());\r\n    }\r\n  \r\n    checkForbidden(url: string): boolean {\r\n        if (!url) {\r\n            return true;\r\n        }\r\n  \r\n        console.log('[SW] Check forbidden', url);\r\n\r\n        return this.ignoredUrls.some((item) =\u003e url.match(item));\r\n    }\r\n\r\n    isExtraType(id: string){\r\n        let result: string | null = null;\r\n        const _self = this;\r\n      \r\n        Object.keys(this.regExTypes).forEach(function(key){\r\n            if(result === null \u0026\u0026 _self.regExTypes[key].exec(id) !== null){\r\n                result = key;\r\n            }\r\n        });\r\n      \r\n        return result;\r\n    }    \r\n\r\n    startServiceWorker(regExTypes: { [key: string]: RegExp }, forbiddenUrls: RegExp[]): JunctionServiceWorker \r\n    {        \r\n        this.tracker = { currentTracker: null };\r\n        this.ignoredUrls = forbiddenUrls;\r\n        this.trackersToSync = [];\r\n        this.regExTypes = regExTypes;       \r\n\r\n        return this;\r\n    }     \r\n\r\n    async onInit(): Promise\u003cvoid\u003e\r\n    {\r\n        const _self: JunctionServiceWorker = this;\r\n        let THE_USER: IJunctionUser | null = null;        \r\n        const toSync: TimeTracker[] = [];\r\n\r\n        let WS_URL: string | null;   \r\n        \r\n        console.log('Initiating ServiceWorker');\r\n\r\n        this.workerScope.addEventListener('message', (event: MSGEvent) =\u003e {\r\n            // console.log(event);\r\n            if(!event.data){\r\n                console.warn('[SW] Got empty message');\r\n                return;\r\n            }  \r\n\r\n            if (event.data.command){\r\n                console.log('[SW] OP Message:', event.data.command);\r\n            \r\n                switch (event.data.command) {\r\n                case 'SET_WS_URL':\r\n                    WS_URL = event.data.params.url;\r\n                    break;\r\n                case 'SET_USER':      \r\n                    if(!this.getUser()){\r\n                        THE_USER = event.data.params;                        \r\n                        this.setUser(THE_USER);\r\n                    }\r\n                    _self.checkWs(WS_URL, this.getUser());\r\n                    break;\r\n                case 'START_TRACKING':\r\n                    _self.checkWs(WS_URL, this.getUser());\r\n                    if(!this.wsService.socket() \u0026\u0026 this.getUser()){\r\n                        break;\r\n                    }\r\n                    _self.trackActivity(event.data.asset_type, event.data.params.page_location, event.data.params, toSync);\r\n                    break;\r\n                case 'TRACKER_SAVED':\r\n                    const { clientId, tracker } = event.data.params;\r\n        \r\n                    _self.sendMessageToClient(clientId, { message: 'TRACKER_SAVED_RESPONSE', data: tracker });\r\n                    break;  \r\n                }\r\n            }\r\n        });  \r\n    }\r\n\r\n    async onActivate(): Promise\u003cvoid\u003e\r\n    {        \r\n        console.log('Activated ServiceWorker');\r\n     \r\n        this.startServiceWorker(this.regExTypes, this.ignoredUrls);\r\n    }\r\n\r\n    private checkWs(WS_URL: string, THE_USER: IJunctionUser): boolean \r\n    {\r\n        if(!this.wsService.socket() \u0026\u0026 WS_URL){\r\n            this.wsService.init(WS_URL, THE_USER);\r\n\r\n            return true;\r\n        }\r\n\r\n        return false;\r\n    };\r\n}\r\n\r\nMyServiceWorker.create();\r\n```\r\n\r\n**We point to this file in webpack / .rws.json \"service_worker\" option**\r\n\r\n## Example: WebChat Component\r\n\r\nThe WebChat component demonstrates a practical use of `APIService` in a real-world scenario. It shows how to send and receive data from the backend.\r\n\r\n### WebChat Component Implementation\r\n\r\n```typescript\r\nimport { RWSViewComponent, ApiService, NotifyService, RWSView, WSService } from '@rws-framework/client';\r\nimport { observable, css  } from '@microsoft/fast-element';\r\n\r\nimport './children/convo-footer/component';\r\n\r\nimport WebChatEvents from './events';\r\nimport { IContext } from './children/left-bar/component';\r\nimport { IMessage } from '../chat-message/component';\r\nimport { ITalkApiResponse, BedrockBaseModel, IHyperParameter, \r\n\r\n@RWSView('web-chat')\r\nclass WebChat extends RWSViewComponent {\r\n\r\n    static fileList: string[] = [\r\n        'svg/icon_talk_1.svg'\r\n    ];\r\n\r\n    @observable messages: IMessage[] = [];\r\n    @observable hyperParameters: { key: string, value: any } | any = {};\r\n    @observable bookId: string = null;\r\n    @observable chapterNr: string = null;\r\n\r\n    @observable chosenModel: BedrockBaseModel = null;\r\n    @observable chatContext: IContext = { label: 'Book chat' };\r\n\r\n    @observable bookModel: IBook = null;\r\n\r\n    @observable minified: boolean = true;\r\n\r\n    @ngAttr custombookid: string = null;\r\n    @ngAttr customchapternr: string = null;\r\n\r\n    @observable customTemperature: number = 0.7;\r\n    @observable customTopK: number = 250;\r\n    @observable customMaxTokensToSample: number = 1024;\r\n    @observable customTopP: number = 0.7;\r\n\r\n    @ngAttr hTemperature?: string = '0.7';\r\n    @ngAttr hTopK?: string = '250';\r\n    @ngAttr hMaxTokensToSample?: string = '1024';\r\n    @ngAttr hTopP?: string = '0.7';\r\n\r\n    @observable convoId: string;\r\n    @observable wsId: string;\r\n\r\n    @ngAttr dev: PseudoBool = 'false';\r\n    @ngAttr opened: PseudoBool = 'false';\r\n\r\n    @ngAttr userImage: string | null = null;\r\n    @ngAttr initials: string | null = 'U';\r\n\r\n    handlers: (this: WebChat) =\u003e IWebChatHandlers = assignHandlers;\r\n    streamCall: (msg: IMessage) =\u003e Promise\u003cvoid\u003e = callStreamApi;\r\n\r\n    getDefaultHyperParams = getDefaultParams;\r\n    setHyperParam = setHyperParam;\r\n\r\n    public msgOptions: IConvoMsgOptions = {\r\n        headerEnabled: false,\r\n        dateEnabled: false\r\n    };\r\n\r\n    connectedCallback() {\r\n        super.connectedCallback();\r\n\r\n        if (this.routeParams?.dev || this.dev === 'true') {\r\n            this.dev = 'true';\r\n        } else {\r\n            this.dev = 'false';\r\n        }\r\n\r\n        this.checkForBookId();\r\n        this.checkForBookChapter();\r\n\r\n        this.chosenModel = ClaudeModel;\r\n\r\n        const provider = this.chosenModel?.providerName?.toLowerCase() || null;\r\n        const defParams = this.getDefaultHyperParams(provider);\r\n\r\n        const defaultParams: { [key: string]: any } = {};\r\n\r\n        Object.keys(defParams).forEach(paramKey =\u003e {\r\n            if (defParams[paramKey]) {\r\n                defaultParams[paramKey] = this.setHyperParam(paramKey, defParams[paramKey]);\r\n            }\r\n        });\r\n\r\n        this.hyperParameters = { ...defaultParams, ...this.hyperParameters };\r\n\r\n        this.wsId = uuid();\r\n\r\n        this.on\u003c{ item: IMessage }\u003e(WebChatEvents.message.send, (event: CustomEvent\u003c{ item: IMessage }\u003e) =\u003e {\r\n\r\n\r\n            this.streamCall(event.detail.item);\r\n        });\r\n\r\n        if (this.routeParams?.opened || this.opened === 'true') {\r\n            this.minified = false;\r\n        }\r\n\r\n        if (this.hTemperature) {\r\n            this.hHandlers.hTemperature(null, this.hTemperature);\r\n        }\r\n\r\n        if (this.hMaxTokensToSample) {\r\n            this.hHandlers.hMaxTokensToSample(null, this.hMaxTokensToSample);\r\n        }\r\n\r\n        if (this.hTopK) {\r\n            this.hHandlers.hTopK(null, this.hTopK);\r\n        }\r\n\r\n        if (this.hTopP) {\r\n            this.hHandlers.hTopP(null, this.hTopP);\r\n        }\r\n    }\r\n    checkForBookId() {\r\n        this.bookId = this.routeParams.bookId || this.custombookid || null;\r\n\r\n        if (this.bookId) {\r\n            this.apiService.back.get\u003cIBook\u003e('train:get:book', { routeParams: { bookId: this.bookId } }).then((data: IBook) =\u003e {\r\n                this.bookModel = data;\r\n            });\r\n        }\r\n    }\r\n\r\n    checkForBookChapter() {\r\n        this.chapterNr = this.routeParams.chapterNr || this.customchapternr || null;\r\n    }\r\n\r\n    custombookidChanged(oldVal: string, newVal: string) {\r\n        if (newVal) {\r\n            this.custombookid = newVal;\r\n            this.checkForBookId();\r\n        } else {\r\n            this.custombookid = null;\r\n        }\r\n    }\r\n\r\n    customchapternrChanged(oldVal: string, newVal: string) {\r\n        if (newVal) {\r\n            this.customchapternr = newVal;\r\n            this.checkForBookChapter();\r\n        } else {\r\n            this.customchapternr = null;\r\n        }\r\n    }\r\n\r\n    devChanged(oldVal: string, newVal: string) {\r\n        if (oldVal !== newVal) {\r\n            this.dev = newVal === 'true' ? 'true' : 'false';\r\n        }\r\n    }\r\n\r\n    hHandlers: IHyperHandler = getParamChangeHandlers.bind(this)();\r\n\r\n    hTemperatureChanged: ChangeHandlerType\u003cstring\u003e = this.hHandlers.hTemperature;\r\n    hMaxTokensToSampleChanged: ChangeHandlerType\u003cstring\u003e = this.hHandlers.hMaxTokensToSample;\r\n    hTopKChanged: ChangeHandlerType\u003cstring\u003e = this.hHandlers.hTopK;\r\n    hTopPChanged: ChangeHandlerType\u003cstring\u003e = this.hHandlers.hTopP;\r\n\r\n    userImageChanged(oldVal: string, newVal: string) {\r\n        if (newVal \u0026\u0026 oldVal !== newVal) {\r\n            this.userImage = newVal;\r\n        }\r\n    }\r\n\r\n    initialsChanged(oldVal: string, newVal: string) {\r\n        if (newVal \u0026\u0026 oldVal !== newVal) {\r\n            this.initials = newVal;\r\n        }\r\n    }\r\n\r\n    convoIdChanged(oldVal: string, newVal: string) {\r\n        if (newVal \u0026\u0026 oldVal !== newVal) {\r\n            console.log(this.convoId);\r\n            this.convoId = newVal;\r\n        }\r\n    }\r\n}\r\n\r\nWebChat.defineComponent();\r\n\r\n\r\nexport { WebChat, IContext };\r\n\r\n```\r\n\r\n### Controller route\r\n\r\nThe route ApiService.back.get|post|put|delete methods can be found in backend controllers:\r\n\r\n```typescript\r\n @Route('talk:models:prompt', 'POST')\r\n    public async modelTalkAction(params: IRequestParams): Promise\u003cITalkApiResponse\u003e\r\n    {\r\n        // (...)\r\n    }       \r\n```\r\n\r\nand src/config/config\r\n\r\n```typescript\r\n    const http_routes = [    \r\n        {\r\n            prefix: '/prefix',\r\n            routes: [\r\n                {\r\n                    name: 'action:route:name',\r\n                    path: '/path/to/action'\r\n                },\r\n                 {\r\n                    name: 'action:route:name',\r\n                    path: '/path/to/action'\r\n                }       \r\n            ]\r\n        },        \r\n        {\r\n            name: 'home:index',\r\n            path: '/*', //if no routes detected pass request to frontend\r\n            noParams: true, //do not read params from the request leave it to the front\r\n        },       \r\n    ]\r\n```\r\n\r\n### Socket route\r\n\r\nSocket route from\r\n\r\n```typescript\r\n WSService.sendMessage\u003cPayloadType\u003e('send_msg', {\r\n      modelId: this.chosenModel.modelId,\r\n      prompt: msg.content\r\n    });\r\n\r\n```\r\n\r\nare defined in backend/src/config/config\r\n\r\n```typescript\r\n    const ws_routes = {\r\n        'send_msg' : ChatSocket,\r\n        'process_book' : TrainSocket,\r\n    }\r\n```\r\n\r\n## Other configs\r\n\r\n### example tsconfig.json\r\n\r\n```json\r\n{\r\n    \"compilerOptions\": {\r\n      \"baseUrl\": \"../\",\r\n      \"experimentalDecorators\": true,\r\n      \"emitDecoratorMetadata\": true,\r\n      \"target\": \"ES2018\",\r\n      \"module\": \"es2022\",       \r\n      \"moduleResolution\": \"node\",     \r\n      \"strict\": true,\r\n      \"esModuleInterop\": true,\r\n      \"sourceMap\": true,\r\n      \"outDir\": \"dist\",\r\n      \"strictNullChecks\": false,    \r\n      \"allowSyntheticDefaultImports\": true,    \r\n      \"lib\": [\"DOM\", \"ESNext\", \"WebWorker\"], \r\n      \"paths\": {        \r\n      }       \r\n    },\r\n    \"include\": [\r\n      \"src\",          \r\n      \"../node_modules/@rws-framework/client/declarations.d.ts\", //TEMPORARILY NEEDED TO WORK\r\n    ],  \r\n    \"exclude\": [\r\n      \"../node_modules/@rws-framework/client/src/tests\"    \r\n    ]\r\n  }\r\n```\r\n\r\n**Remember to have lib field set in tsconfig.json**\r\n\r\n```json\r\n{\r\n \"lib\": [\"DOM\", \"ESNext\"]\r\n}\r\n```\r\n\r\n## Plugin system\r\n\r\n[PLUGIN SYSTEM README](https://github.com/papablack/rws-client/blob/master/PLUGINS.md)\r\n\r\n## Links\r\n- https://www.fast.design/docs/fast-element/getting-started ( Base FAST documentation, mostly valid not considering passing styles and templates as RWS handles it with Webpack loaders )\r\n- https://www.webcomponents.org (open-source WebComponents repository)","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapablack%2Frws-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpapablack%2Frws-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapablack%2Frws-client/lists"}