@rws-framework/client 2.22.1 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,68 +1,61 @@
1
- # Realtime Web Suit client setup and configuration guide
1
+ # @rws-framework/client
2
2
 
3
- Realtime 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.
3
+ This package provides the core client-side framework for Realtime Web Suit (RWS), enabling modular, asynchronous web components, state management, and integration with backend services. It is located in `.dev/client`.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
7
  1. [Overview](#overview)
8
8
  2. [Getting Started](#getting-started)
9
- 3. [Key Components: RWSClient & RoutingService](#key-components-rwsclient--routingservice)
9
+ 3. [Key Concepts](#key-concepts)
10
10
  4. [Component Initialization](#component-initialization)
11
- 5. [DI](#dependency-injection)
12
- 6. [Frontend routes](#frontend-routes)
11
+ 5. [Dependency Injection](#dependency-injection)
12
+ 6. [Frontend Routes](#frontend-routes)
13
13
  7. [Backend Imports](#backend-imports)
14
14
  8. [Utilizing APIService](#utilizing-apiservice)
15
15
  9. [Notifier](#notifier)
16
16
  10. [Service Worker](#service-worker)
17
17
  11. [Example: WebChat Component](#example-webchat-component)
18
18
  12. [Other configs](#other-configs)
19
- 13. [Plugins](#plugin-system)
20
- 14. [Links](#links)
19
+ 13. [Plugin System](#plugin-system)
20
+ 14. [Nest Interconnectors](#nest-interconnectors)
21
+ 15. [Styles Injection](#styles-injection)
22
+ 16. [Links](#links)
21
23
 
22
24
  ## Overview
23
25
 
24
- The 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.
26
+ `@rws-framework/client` is the heart of the RWS frontend framework. It manages configuration, plugin management, service registration, application lifecycle, and provides the base for all RWSView components. It is designed for modular, fullstack-oriented web applications using the RWS/FAST paradigm.
25
27
 
26
- ## Getting Started
28
+ ### Main Features
29
+ - **RWSClient**: Main entry point for the frontend application. Handles configuration, plugin management, service registration, and application lifecycle.
30
+ - **Dependency Injection**: Uses a DI container for services and components.
31
+ - **Component Registration**: Supports modular component definition and registration.
32
+ - **Plugin System**: Add plugins for routing, websockets, and more.
33
+ - **Notifier**: Customizable notification system for UI messages.
34
+ - **Service Worker Integration**: Pushes config and user data to service workers.
35
+ - **User and Config Management**: Handles user state and application configuration.
36
+ - **API Service**: Integrates with backend API routes and authentication.
27
37
 
28
- To 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.
38
+ ## Getting Started
29
39
 
30
- from your project dir do:
40
+ Install dependencies and initialize the project:
31
41
 
32
42
  ```bash
33
43
  yarn
34
- ```
35
-
36
- Initiate cfg files and webpack build:
37
- ```bash
38
44
  rws-client init
45
+ yarn build # or yarn watch for dev
46
+ yarn server # to just start server
39
47
  ```
40
48
 
41
- to install once and then to build after preparing components:
42
-
43
- ```bash
44
- yarn build
45
- ```
46
- or to watch for dev
47
-
48
- ```bash
49
- yarn watch
50
- ```
51
- or to just start server
52
- ```bash
53
- yarn server
54
- ```
55
-
56
- then start engine in the site javascript (can be inline):
49
+ Start the engine in your site JavaScript:
57
50
 
58
51
  ```javascript
59
- window.RWS.client.start(CFG); // it is async function
52
+ window.RWS.client.start(CFG); // async function
60
53
  ```
61
54
 
62
- *or for initial setup then start on certain event (example)*
55
+ Or for initial setup on an event:
63
56
 
64
57
  ```javascript
65
- window.RWS.client.setup(CFG).then(() => { // it is async function
58
+ window.RWS.client.setup(CFG).then(() => {
66
59
  $.on('loaded', function(data){
67
60
  const optionalNewCfg = { backendRoutes: data.backendRoutes };
68
61
  window.RWSClient.start(optionalNewCfg).then();
@@ -70,11 +63,10 @@ window.RWS.client.setup(CFG).then(() => { // it is async function
70
63
  });
71
64
  ```
72
65
 
73
- ### default config for RWS:
66
+ ### Default config for RWS
74
67
 
75
68
  ```javascript
76
69
  const _DEFAULT_CONFIG_VARS = {
77
- //Build configs
78
70
  dev: false,
79
71
  hot: false,
80
72
  report: false,
@@ -82,943 +74,239 @@ const _DEFAULT_CONFIG_VARS = {
82
74
  publicIndex: 'index.html',
83
75
  outputFileName: 'client.rws.js',
84
76
  outputDir: process.cwd() + '/build',
85
- //Frontend RWS client configs
86
77
  backendUrl: null,
87
78
  wsUrl: null,
88
79
  partedDirUrlPrefix: '/lib/rws',
89
80
  partedPrefix: 'rws',
90
81
  pubUrlFilePrefix: '/',
91
- //Universal configs
92
- transports: ['websocket'],
93
82
  parted: false,
94
83
  }
95
-
96
- ```
97
-
98
- *The options description:*
99
-
100
- | **Option** | **Description** | **Default** |
101
- |--------------|-----------------|---------------|
102
- | backendUrl | Url for backend integration (API calls) | null |
103
- | wsUrl | Url for backend integration (Websocket calls) | null |
104
- | backendRoutes | Backend routes object imported from backend node for integration with API calls | null |
105
- | apiPrefix | Prefix for API calls | / |
106
- | routes | Routes for frontend routing | {} |
107
- | transports | Websockets transports method | ['websockets'] |
108
- | user | User object for backend auth / frontend data source | null |
109
- | ignoreRWSComponents | Do not declare base RWS components (uploader, progress) | false |
110
- | pubUrlFilePrefix | the url for accessing files from browser URL | / |
111
- | pubUrl | the url for accessing public dir from browser URL | / |
112
- | outputDir | build dir | ./build |
113
- | outputFileName | output file name | rws.client.js |
114
- | publicDir | public dir for HTML serving | ./public |
115
- | tsConfigPath | tsconfig.json path | ./tsconfig.njson |
116
- | entry | default TS entry for transpilation | ./src/index.ts |
117
- | 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 |
118
- | partedPrefix | parted file prefix ([prefix].[cmp name].js) | rws |
119
- | partedDirUrlPrefix | URL for generated js parted files directory | / |
120
- | copyAssets | An option for defining structure that will be copied after build | {} |
121
-
122
- *copyAssets example*
123
-
124
- ```json
125
- "copyAssets": {
126
- "./public/js/": [ // target directory
127
- "./build/", // copy this directory to target
128
- "./src/styles/compiled/main.css" //copy this file to target
129
- ]
130
- }
131
- ```
132
-
133
- ### The FRONT config TS interface:
134
-
135
- ```typescript
136
- interface IRWSConfig {
137
- defaultLayout?: typeof RWSViewComponent
138
- backendUrl?: string
139
- wsUrl?: string
140
- backendRoutes?: any[]
141
- apiPrefix?: string
142
- routes?: IFrontRoutes
143
- transports?: string[]
144
- user?: any
145
- ignoreRWSComponents?: boolean
146
- pubUrl?: string
147
- pubUrlFilePrefix?: string
148
- partedDirUrlPrefix?: string
149
- dontPushToSW?: boolean
150
- parted?: boolean
151
- partedFileDir?: string
152
- partedPrefix?: string
153
- routing_enabled?: boolean
154
- _noLoad?: boolean
155
- }
156
- ```
157
- ### The FRONT webpack config:
158
-
159
- ```javascript
160
- const path = require('path');
161
-
162
- const RWSWebpackWrapper = require('@rws-framework/client/rws.webpack.config');
163
-
164
-
165
- const executionDir = process.cwd();
166
-
167
- module.exports = RWSWebpackWrapper({
168
- tsConfigPath: executionDir + '/tsconfig.json',
169
- entry: `${executionDir}/src/index.ts`,
170
- publicDir: path.resolve(executionDir, 'public'),
171
- outputDir: path.resolve(executionDir, 'build'),
172
- outputFileName: 'jtrainer.client.js'
173
- });
174
84
  ```
175
85
 
86
+ *See the table in the original README for all config options.*
176
87
 
177
-
178
- ## Key Components
88
+ ## Key Concepts
179
89
 
180
90
  ### RWSClient
181
- ##
182
- `RWS.client` is the heart of the framework, managing configuration and initialization. It sets up routes, backend connections, and other essential framework services.
183
-
184
-
185
- ### RoutingService
186
-
187
- `RoutingService` handles the navigation and routing within your application. It ensures that URL changes reflect the correct component rendering.
188
-
189
- **Depreciation Notice**
190
-
191
- ***RoutingService will be moved to @rws-framework/browser-router near future***
192
-
193
- ### WSService
194
-
195
- `WSService` handles Websockets messenging to the backend.
196
-
197
- **Depreciation Notice**
198
- ***WSService will be moved to @rws-framework/nest-interconnectors in near future***
199
-
200
- ### APIService
91
+ `RWSClient` is the main class, instantiated and managed via DI. It manages configuration, plugins, services, and the application lifecycle.
201
92
 
202
- `APIService` handles API requests to the backend.
203
-
204
- Implementing the Framework
205
-
206
- **Main File:**
207
-
208
- The main file (`index.ts`) is where you initialize the RWSClient. Here, you configure your routes, backend routes, and component initializations.
209
-
210
- Following is example of full usage of the framework
93
+ #### Example Usage
211
94
 
212
95
  ```typescript
213
- async function initializeApp() {
214
- const theClient = RWSContainer().get(RWSClient);
215
-
216
- theClient.addRoutes(frontendRoutes);
217
- theClient.setBackendRoutes(backendRoutes());
218
-
219
- theClient.enableRouting();
220
-
221
- theClient.onInit(async () => {
222
-
223
- // For single file output:
224
- initComponents(theClient.appConfig.get('parted')); // start components for monolith mode
225
- theClient.defineComponents(); // start RWS conponents
226
-
227
- //custom outside components registering
228
- provideFASTDesignSystem()
229
- .register(fastButton())
230
- .register(fastTab())
231
- .register(fastSlider())
232
- .register(fastSelect())
233
- .register(fastDivider())
234
- .register(fastMenu())
235
- .register(fastMenuItem())
236
- ;
237
-
238
- // Service worker code
239
- // const swFilePath: string = `${theClient.appConfig.get('pubUrl')}/service_worker.js`;
240
-
241
- // await theClient.swService.registerServiceWorker();
242
-
243
- //if(theClient.getUser()){
244
- // theClient.pushUserToServiceWorker({...theClient.getUser(), instructor: false});
245
- //}
246
-
247
- });
248
-
249
- theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void, notifierOptions: any = {}) => {
250
- switch(uiType){
251
- case 'notification':
252
- let notifType = 'success';
253
-
254
- if(logType === 'error'){
255
- notifType = 'error';
256
- }
257
-
258
- if(logType === 'warning'){
259
- notifType = 'warning';
260
- }
261
-
262
- return alertify.notify(message, notifType, 5, onConfirm);
263
-
264
- case 'alert':
265
- const alertObj = alertify.alert('Junction AI Notification', message, onConfirm);
266
-
267
- Object.keys(notifierOptions).forEach(key => {
268
- const optionValue = notifierOptions[key];
269
-
270
- if(key === 'width'){
271
-
272
- alertObj.elements.dialog.style = `max-width: ${optionValue};`;
273
-
274
- return;
275
- }
276
-
277
- alertObj.set(key, optionValue);
278
- });
279
-
280
- alertObj.show();
281
-
282
- return alertObj;
283
- case 'silent':
284
- if(logType == 'warning'){
285
- console.warn(message);
286
- }else if(logType == 'error'){
287
- console.error(message);
288
- }else{
289
- console.log(message);
290
- }
291
- return;
292
- }
293
- });
294
-
295
- theClient.assignClientToBrowser();
296
- }
297
-
298
- initializeApp().catch(console.error);
299
- ```
96
+ import RWSClient, { RWSClientInstance } from '@rws-framework/client';
300
97
 
301
- ## Component Initialization
98
+ const theClient: RWSClientInstance = RWSContainer().get(RWSClient);
302
99
 
303
- In `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.
100
+ theClient.addPlugin(RWSBrowserRouter);
101
+ theClient.addPlugin(RWSWebsocketsPlugin, { enabled: true });
304
102
 
305
- **This should be conditioned not to execute imported code when using parted mode.**
103
+ theClient.assignClientToBrowser();
306
104
 
307
- ### Default component structure
105
+ theClient.onInit(async () => {
106
+ // Register components, routes, etc.
107
+ });
308
108
 
309
- ```
310
- component-dir/
311
- component.ts
312
- template.html
313
- styles/
314
- layout.scss
315
- ```
109
+ theClient.setNotifier((message, logType) => {
110
+ // Custom notification logic
111
+ });
316
112
 
317
- **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**
318
-
319
- ```html
320
- <div class="convo-area-wrap">
321
- <header>
322
- <div class="header-inner"></div>
323
- ${T.when(x => x.noChoose === 'false', (item, index) => T.html`<div>
324
- <chat-convo-models :chosenModel="${x => x.chosenModel}"></chat-convo-models>
325
- </div>`)}
326
- <div>
327
- <h2>${ x => x.chatContext ? x.chatContext.label : 'loading...' }</h2>
328
- <h3><strong>${ x => x.messageList.length }</strong> messages in total</h3>
329
- </div>
330
- <fast-divider></fast-divider>
331
- </header>
332
- <section>
333
- <div class="scroll-area">
334
- <div class="scroll-content">
335
- ${T.repeat(x => x.messageList, (item, index) => T.html`
336
- <chat-convo-message :contentReturn="${item => item}" :item="${item => item}"/>
337
- `)}
338
-
339
- ${T.when(x => !x.messageList.length, (item, index) => T.html`
340
- <p class="no-chat">No messages</p>
341
- `)}
342
- </div>
343
- </div>
344
- </section>
345
-
346
- </div>
113
+ theClient.start({
114
+ backendRoutes,
115
+ backendUrl: process.env.BACKEND_URL,
116
+ wsUrl: process.env.WS_URL,
117
+ hot: true,
118
+ parted: false
119
+ });
347
120
  ```
348
121
 
349
- ### application/_initComponents.ts
350
-
351
- Only if parted mode is false.
122
+ ### Component Registration
352
123
 
353
- ```typescript
354
- import { ChatNav } from '../components/chat-nav/component';
355
- import { DefaultLayout } from '../components/default-layout/component';
356
- import { RWSIcon } from '../components/rws-icon/component';
357
- import { LineSplitter } from '../components/line-splitter/component';
358
- import { WebChat } from '../components/webchat/component';
359
-
360
- export default (partedMode: boolean = false) => {
361
- if(!partedMode){
362
- WebChat;
363
- LineSplitter;
364
- DefaultLayout;
365
- ChatNav;
366
- RWSIcon;
367
- }
368
- };
369
- ```
124
+ - Components must extend `RWSViewComponent` and use the `@RWSView` decorator.
125
+ - Structure: `component-dir/component.ts`, `template.html`, `styles/layout.scss`
126
+ - See RWSDocs for more details.
370
127
 
371
- ## RWS Decorators
128
+ ### Plugin System
372
129
 
373
- **Component needs to extend RWSViewComponent and use @RWSView decorator**:
130
+ - Add plugins via `addPlugin` (e.g., routing, websockets).
131
+ - Plugins are initialized on client startup.
374
132
 
375
- ```typescript
376
- import { RWSViewComponent, RWSView, observable, attr } from '@rws-framework/client';
377
-
378
- const options?: RWSDecoratorOptions;
379
-
380
- @RWSView('tag-name', options)
381
- class WebChat extends RWSViewComponent {
382
- @attr tagAttr: string; //HTML tag attr
383
- @ngAttr fromNgAttr: string; //HTML attr from angular template
384
- @externalAttr fromExAttr: string; //HTML attr with change observation
385
- @sanitizedAttr htmlAttr: string; //HTML attr that's sanitized with every val change
386
- @observable someVar: any; //Var for templates/value change observation
387
- @externalObservable someExVar: string; //Var for templates/value change observation with external watch
388
- }
389
- ```
133
+ ### Notifier
390
134
 
391
- The decorator options type:
135
+ Set a custom notification handler with `setNotifier`:
392
136
 
393
137
  ```typescript
394
- interface RWSDecoratorOptions{
395
- template?: string, //relative path to HTML template file (default: ./template.html)
396
- styles?: string //relative path to SCSS file (./styles/layout.scss)
397
- fastElementOptions?: any //the stuff you would insert into static definition in FASTElement class.
398
- }
399
-
138
+ theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void) => {
139
+ // Implementation based on uiType
140
+ });
400
141
  ```
401
142
 
402
- # Dependency Injection
143
+ ### Service Worker
403
144
 
404
- ## Default service usage:
145
+ If you pass `{serviceWorker: 'service_worker_class_path.ts'}` to the RWS Webpack wrapper, the code will build a ServiceWorker to pubDir.
405
146
 
406
- ```typescript
407
- import { RWSViewComponent, RWSView } from 'rws-js-client';
147
+ ## Dependency Injection
408
148
 
409
- @RWSView('your-tag');
410
- class YourComponent extends RWSViewComponent {
411
- someMethod(url: string): void
412
- {
413
- this.apiService.get(url);
414
- }
415
- }
416
-
417
- ```
149
+ All services and components are registered and resolved via a DI container. Default and custom services can be injected and used throughout your app.
418
150
 
419
- A default service can be used in legacy like this:
151
+ ## Frontend Routes
420
152
 
421
- ```javascript
422
- window.RWS.client.get('ApiService').dateMethodFromRWS();
423
- ```
424
-
425
- Default services: https://github.com/papablack/rws-client/blob/7d16d9c6d83c81c9fe470eb0f507756bc6c71b35/src/components/_component.ts#L58
426
-
427
- ## Custom service usage:
153
+ Define frontend routes using `renderRouteComponent` and pass them to the router plugin. Example route definitions for use with the router component (see `.dev/router`):
428
154
 
429
155
  ```typescript
430
- import {
431
- RWSView, RWSViewComponent, RWSInject,
432
- DateService, DateServiceInstance
433
- } from 'rws-js-client';
434
-
435
- import DateService, {DateServiceInstance} from '../../my-custom-services/DateService';
156
+ import { renderRouteComponent } from '@rws-framework/browser-router';
157
+ import { HomePage } from './pages/home/component';
158
+ import { CompanyList } from './pages/company/list/component';
159
+ import { GeneralSettings } from './pages/settings/general/component';
436
160
 
437
-
438
- @RWSView('your-tag')
439
- class YourComponent extends RWSViewComponent {
440
- //usage in props:
441
- private @RWSInject(ServiceFASTDIPointer) serviceProperty: ServiceClassType;
442
-
443
- //usage in constructor:
444
- constructor(
445
- private @RWSInject(DateService) protected dateService: DateServiceInstance
446
- ) {
447
- super();
448
- }
449
-
450
- someMethod(url: string): void
161
+ export const frontRoutes = [
162
+ {
163
+ path: '/',
164
+ name: 'Home',
165
+ component: HomePage,
166
+ icon: 'home',
167
+ inMenu: true
168
+ },
451
169
  {
452
- this.dateService.get(url);
170
+ path: '/company/list',
171
+ name: 'Companies',
172
+ component: CompanyList,
173
+ icon: 'company',
174
+ inMenu: true
175
+ },
176
+ {
177
+ path: '/settings/general',
178
+ name: 'Settings',
179
+ component: GeneralSettings,
180
+ icon: 'settings',
181
+ inMenu: true
453
182
  }
454
- }
455
-
456
- ```
457
-
458
- Custom service needs to export .getSingleton() as default export and have service class exported as classic export for TS typing:
459
-
460
- ```typescript
461
- import { RWSService } from '@rws-framework/client';
183
+ ];
462
184
 
463
-
464
- class DateService extends RWSService {
465
- static _IN_CLIENT: boolean = true //If set engine will let legacy use the service through RWSClient.get method
466
- //(...)
185
+ // Convert to route map for the router
186
+ const routeMap = {};
187
+ for (const route of frontRoutes) {
188
+ routeMap[route.path] = renderRouteComponent(route.name, route.component);
467
189
  }
468
190
 
469
- export default DateService.getSingleton(); // Fast DI service pointer (it points to instanced service in DI container)
470
- export { DateService as DateServiceInstance }; // the custom service class type
191
+ export default routeMap;
471
192
  ```
472
193
 
473
- **Custom service for legacy**
474
-
475
- If service has static **_IN_CLIENT** set for **true** you can use it like this:
476
-
477
- ```javascript
478
- window.RWS.client.get('DateService').dateMethodFromRWS();
479
- ```
480
-
481
- ## Frontend routes
482
-
483
- if you are passing routes this is example routing file for frontend:
484
-
485
- ```typescript
486
- export default {
487
- '/': renderRouteComponent('Home page', WebChat),
488
- '/the/path': renderRouteComponent('Component title', ComponentClassName),
489
- }
490
- ```
491
-
492
- Router tag:
493
-
494
- ```html
495
- <section>
496
- <rws-router></rws-router>
497
- </section>
498
- ```
194
+ - Each route object can include `path`, `name`, `component`, `icon`, and `inMenu` fields.
195
+ - Use `renderRouteComponent` to wrap the component for the router.
196
+ - Pass the resulting `routeMap` to the router plugin or RWS client configuration.
499
197
 
500
198
  ## Backend Imports
501
199
 
502
- `backendImports.ts` consolidates various backend interfaces, routes, and models, allowing for a synchronized frontend and backend from package https://github.com/papablack/rws
503
-
504
- ```typescript
505
- import IBook from '../../backend/src/models/interfaces/IBook';
506
-
507
- import {
508
- IBookInfo,
509
- } from '../../backend/src/interfaces/IBookInfo';
510
-
511
- import backendRoutes from '../../backend/src/routing/routes';
512
-
513
- export {
514
- IBook,
515
- IBookInfo,
516
- backendRoutes
517
- }
518
-
519
- ```
200
+ `backendImport.ts` consolidates backend interfaces, routes, and models for synchronized frontend/backend development.
520
201
 
521
- usage:
202
+ ### HTTPRoutes Interface
522
203
 
204
+ The RWS client uses a typed interface for backend HTTP routes, typically called `IBackendRoute` or `HTTPRoutes`. This interface defines the structure for backend API endpoints, allowing for type-safe integration between frontend and backend. You can import and use these routes as follows:
523
205
 
524
206
  ```typescript
525
- import { backendRoutes} from '../../backendImport';
526
-
527
- //index.ts
528
- const theClient = new RWSClient();
529
- theClient.setBackendRoutes(backendRoutes());
207
+ import { backendRoutes } from './backendImport';
530
208
 
209
+ theClient.setBackendRoutes(backendRoutes);
531
210
  ```
532
211
 
212
+ - Each route entry in `backendRoutes` should conform to the `IBackendRoute` (or `HTTPRoutes`) interface, describing the HTTP method, path, and any metadata required for API calls.
213
+ - This enables strong typing and autocompletion for API requests throughout your frontend codebase.
533
214
 
534
215
  ## Utilizing APIService
535
216
 
536
- `APIService` is used for making HTTP requests to the backend. It simplifies the process of interacting with your API endpoints.
537
-
538
- after control method we have dynamic types those are: <**ResponseType**, **PayloadType**>
217
+ `APIService` is used for making HTTP requests to the backend. It supports dynamic types for response and payload, and can be accessed via DI or through the RWS client instance.
539
218
 
540
- Example Usage by controller route
219
+ ### Basic Usage
541
220
 
542
221
  ```typescript
543
- const apiPromise: Promise<ITalkApiResponse> = this.apiService.back.post<ITalkApiResponse, IApiTalkPayload>('talk:models:prompt', {
544
- message: msg,
545
- model: this.chosenModel,
546
- });
547
- ```
548
-
549
- Example Usage by url
222
+ // Injected in a component or service
223
+ @RWSInject(ApiService, true) protected apiService: ApiServiceInstance;
550
224
 
551
- ```typescript
552
- const apiPromise: Promise<ITalkApiResponse> = this.apiService.post<ITalkApiResponse, IApiTalkPayload>('/api/path/to/action', {
553
- message: msg,
554
- model: this.chosenModel,
555
- });
225
+ // Or via the client
226
+ const apiService = window.RWS.client.get('ApiService');
556
227
  ```
557
228
 
558
- ## Notifier
229
+ ### Making Requests
559
230
 
560
- ### Overview
561
-
562
- The 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.
563
- Usage
564
-
565
- ### Setting the Notifier
231
+ You can use RESTful methods directly:
566
232
 
567
233
  ```typescript
568
- theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void) => {
569
- // Implementation based on uiType
570
- });
234
+ // GET request
235
+ apiService.get('/api/some-endpoint');
571
236
 
237
+ // POST request with payload
238
+ type MyResponse = { ... };
239
+ type MyPayload = { ... };
240
+ const result = await apiService.post<MyResponse, MyPayload>('/api/some-endpoint', { foo: 'bar' });
572
241
  ```
573
242
 
574
- This function allows setting a custom notifier in the RWS Client. It handles the logic based on `uiType`.
575
-
576
- Alert, Notify, and Silent
577
-
578
- - alert: Displays an alert dialog with the message.
579
- - notify: Shows a notification with the message.
580
- - silent: Silently logs the message to the console.
243
+ ### Using Named Backend Routes
581
244
 
582
- Each method can be configured with a `message`, `logType`, and an optional `onConfirm` callback function.
583
-
584
- Note
585
-
586
- Ensure 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.
587
-
588
- ## Service Worker
589
-
590
- If you pass ```{serviceWorker: 'service_worker_class_path.ts'}``` to RWS Webpack wrapper function param, the code will build ServiceWorker to pubDir.
591
-
592
- example ServiceWorker class:
245
+ If you use named backend routes (from `backendRoutes`):
593
246
 
594
247
  ```typescript
595
- import SWService, { ServiceWorkerServiceInstance } from '@rws-framework/client/src/services/ServiceWorkerService'
596
- import {TimeTracker} from '../services/TimeTrackerService';
597
- import RWSServiceWorker from '@rws-framework/client/src/service_worker/src/_service_worker';
598
- import { RWSWSService as WSService } from '@rws-framework/client/src/services/WSService'
599
-
600
- declare const self: ServiceWorkerGlobalScope;
601
-
602
- class MyServiceWorker extends RWSServiceWorker {
603
- public tracker: { currentTracker: TimeTracker | null };
604
- public trackersToSync: TimeTracker[];
605
-
606
- protected regExTypes: { [key: string]: RegExp } = {
607
- SOME_VIEW: new RegExp('.*:\\/\\/.*\\/#\\/([a-z0-9].*)\\/route\\/action$')
608
- };
609
- ignoredUrls = [
610
- new RegExp('(.*(?=.[^.]*$).*)/#/login'),
611
- new RegExp('(.*(?=.[^.]*$).*)/#/logout'),
612
- ];
613
-
614
- constructor(){
615
- super(self, RWSContainer());
616
- }
617
-
618
- checkForbidden(url: string): boolean {
619
- if (!url) {
620
- return true;
621
- }
622
-
623
- console.log('[SW] Check forbidden', url);
624
-
625
- return this.ignoredUrls.some((item) => url.match(item));
626
- }
627
-
628
- isExtraType(id: string){
629
- let result: string | null = null;
630
- const _self = this;
631
-
632
- Object.keys(this.regExTypes).forEach(function(key){
633
- if(result === null && _self.regExTypes[key].exec(id) !== null){
634
- result = key;
635
- }
636
- });
637
-
638
- return result;
639
- }
640
-
641
- startServiceWorker(regExTypes: { [key: string]: RegExp }, forbiddenUrls: RegExp[]): JunctionServiceWorker
642
- {
643
- this.tracker = { currentTracker: null };
644
- this.ignoredUrls = forbiddenUrls;
645
- this.trackersToSync = [];
646
- this.regExTypes = regExTypes;
647
-
648
- return this;
649
- }
650
-
651
- async onInit(): Promise<void>
652
- {
653
- const _self: JunctionServiceWorker = this;
654
- let THE_USER: IJunctionUser | null = null;
655
- const toSync: TimeTracker[] = [];
656
-
657
- let WS_URL: string | null;
658
-
659
- console.log('Initiating ServiceWorker');
660
-
661
- this.workerScope.addEventListener('message', (event: MSGEvent) => {
662
- // console.log(event);
663
- if(!event.data){
664
- console.warn('[SW] Got empty message');
665
- return;
666
- }
667
-
668
- if (event.data.command){
669
- console.log('[SW] OP Message:', event.data.command);
670
-
671
- switch (event.data.command) {
672
- case 'SET_WS_URL':
673
- WS_URL = event.data.params.url;
674
- break;
675
- case 'SET_USER':
676
- if(!this.getUser()){
677
- THE_USER = event.data.params;
678
- this.setUser(THE_USER);
679
- }
680
- _self.checkWs(WS_URL, this.getUser());
681
- break;
682
- case 'START_TRACKING':
683
- _self.checkWs(WS_URL, this.getUser());
684
- if(!this.wsService.socket() && this.getUser()){
685
- break;
686
- }
687
- _self.trackActivity(event.data.asset_type, event.data.params.page_location, event.data.params, toSync);
688
- break;
689
- case 'TRACKER_SAVED':
690
- const { clientId, tracker } = event.data.params;
691
-
692
- _self.sendMessageToClient(clientId, { message: 'TRACKER_SAVED_RESPONSE', data: tracker });
693
- break;
694
- }
695
- }
696
- });
697
- }
698
-
699
- async onActivate(): Promise<void>
700
- {
701
- console.log('Activated ServiceWorker');
702
-
703
- this.startServiceWorker(this.regExTypes, this.ignoredUrls);
704
- }
705
-
706
- private checkWs(WS_URL: string, THE_USER: IJunctionUser): boolean
707
- {
708
- if(!this.wsService.socket() && WS_URL){
709
- this.wsService.init(WS_URL, THE_USER);
710
-
711
- return true;
712
- }
713
-
714
- return false;
715
- };
716
- }
248
+ // By route name (controller:action)
249
+ const data = await apiService.back.get<MyResponse>('user:getProfile', { routeParams: { id: '123' } });
717
250
 
718
- MyServiceWorker.create();
251
+ // POST with payload
252
+ type Payload = { name: string };
253
+ const result = await apiService.back.post<MyResponse, Payload>('user:updateProfile', { name: 'John' });
719
254
  ```
720
255
 
721
- **We point to this file in webpack / .rws.json "service_worker" option**
722
-
723
- ## Example: WebChat Component
724
-
725
- The WebChat component demonstrates a practical use of `APIService` in a real-world scenario. It shows how to send and receive data from the backend.
726
-
727
- ### WebChat Component Implementation
256
+ ### File Upload Example
728
257
 
729
258
  ```typescript
730
- import { RWSViewComponent, ApiService, NotifyService, RWSView, WSService } from '@rws-framework/client';
731
- import { observable, css } from '@microsoft/fast-element';
732
-
733
- import './children/convo-footer/component';
734
-
735
- import WebChatEvents from './events';
736
- import { IContext } from './children/left-bar/component';
737
- import { IMessage } from '../chat-message/component';
738
- import { ITalkApiResponse, BedrockBaseModel, IHyperParameter,
739
-
740
- @RWSView('web-chat')
741
- class WebChat extends RWSViewComponent {
742
-
743
- static fileList: string[] = [
744
- 'svg/icon_talk_1.svg'
745
- ];
746
-
747
- @observable messages: IMessage[] = [];
748
- @observable hyperParameters: { key: string, value: any } | any = {};
749
- @observable bookId: string = null;
750
- @observable chapterNr: string = null;
751
-
752
- @observable chosenModel: BedrockBaseModel = null;
753
- @observable chatContext: IContext = { label: 'Book chat' };
754
-
755
- @observable bookModel: IBook = null;
756
-
757
- @observable minified: boolean = true;
758
-
759
- @ngAttr custombookid: string = null;
760
- @ngAttr customchapternr: string = null;
761
-
762
- @observable customTemperature: number = 0.7;
763
- @observable customTopK: number = 250;
764
- @observable customMaxTokensToSample: number = 1024;
765
- @observable customTopP: number = 0.7;
766
-
767
- @ngAttr hTemperature?: string = '0.7';
768
- @ngAttr hTopK?: string = '250';
769
- @ngAttr hMaxTokensToSample?: string = '1024';
770
- @ngAttr hTopP?: string = '0.7';
771
-
772
- @observable convoId: string;
773
- @observable wsId: string;
774
-
775
- @ngAttr dev: PseudoBool = 'false';
776
- @ngAttr opened: PseudoBool = 'false';
777
-
778
- @ngAttr userImage: string | null = null;
779
- @ngAttr initials: string | null = 'U';
780
-
781
- handlers: (this: WebChat) => IWebChatHandlers = assignHandlers;
782
- streamCall: (msg: IMessage) => Promise<void> = callStreamApi;
783
-
784
- getDefaultHyperParams = getDefaultParams;
785
- setHyperParam = setHyperParam;
786
-
787
- public msgOptions: IConvoMsgOptions = {
788
- headerEnabled: false,
789
- dateEnabled: false
790
- };
791
-
792
- connectedCallback() {
793
- super.connectedCallback();
794
-
795
- if (this.routeParams?.dev || this.dev === 'true') {
796
- this.dev = 'true';
797
- } else {
798
- this.dev = 'false';
799
- }
800
-
801
- this.checkForBookId();
802
- this.checkForBookChapter();
803
-
804
- this.chosenModel = ClaudeModel;
805
-
806
- const provider = this.chosenModel?.providerName?.toLowerCase() || null;
807
- const defParams = this.getDefaultHyperParams(provider);
808
-
809
- const defaultParams: { [key: string]: any } = {};
810
-
811
- Object.keys(defParams).forEach(paramKey => {
812
- if (defParams[paramKey]) {
813
- defaultParams[paramKey] = this.setHyperParam(paramKey, defParams[paramKey]);
814
- }
815
- });
816
-
817
- this.hyperParameters = { ...defaultParams, ...this.hyperParameters };
818
-
819
- this.wsId = uuid();
820
-
821
- this.on<{ item: IMessage }>(WebChatEvents.message.send, (event: CustomEvent<{ item: IMessage }>) => {
822
-
823
-
824
- this.streamCall(event.detail.item);
825
- });
826
-
827
- if (this.routeParams?.opened || this.opened === 'true') {
828
- this.minified = false;
829
- }
830
-
831
- if (this.hTemperature) {
832
- this.hHandlers.hTemperature(null, this.hTemperature);
833
- }
834
-
835
- if (this.hMaxTokensToSample) {
836
- this.hHandlers.hMaxTokensToSample(null, this.hMaxTokensToSample);
837
- }
838
-
839
- if (this.hTopK) {
840
- this.hHandlers.hTopK(null, this.hTopK);
841
- }
842
-
843
- if (this.hTopP) {
844
- this.hHandlers.hTopP(null, this.hTopP);
845
- }
846
- }
847
- checkForBookId() {
848
- this.bookId = this.routeParams.bookId || this.custombookid || null;
849
-
850
- if (this.bookId) {
851
- this.apiService.back.get<IBook>('train:get:book', { routeParams: { bookId: this.bookId } }).then((data: IBook) => {
852
- this.bookModel = data;
853
- });
854
- }
855
- }
856
-
857
- checkForBookChapter() {
858
- this.chapterNr = this.routeParams.chapterNr || this.customchapternr || null;
859
- }
860
-
861
- custombookidChanged(oldVal: string, newVal: string) {
862
- if (newVal) {
863
- this.custombookid = newVal;
864
- this.checkForBookId();
865
- } else {
866
- this.custombookid = null;
867
- }
868
- }
869
-
870
- customchapternrChanged(oldVal: string, newVal: string) {
871
- if (newVal) {
872
- this.customchapternr = newVal;
873
- this.checkForBookChapter();
874
- } else {
875
- this.customchapternr = null;
876
- }
877
- }
878
-
879
- devChanged(oldVal: string, newVal: string) {
880
- if (oldVal !== newVal) {
881
- this.dev = newVal === 'true' ? 'true' : 'false';
882
- }
883
- }
884
-
885
- hHandlers: IHyperHandler = getParamChangeHandlers.bind(this)();
259
+ await apiService.uploadFile('/api/upload', file, progress => {
260
+ console.log('Progress:', progress);
261
+ });
262
+ ```
886
263
 
887
- hTemperatureChanged: ChangeHandlerType<string> = this.hHandlers.hTemperature;
888
- hMaxTokensToSampleChanged: ChangeHandlerType<string> = this.hHandlers.hMaxTokensToSample;
889
- hTopKChanged: ChangeHandlerType<string> = this.hHandlers.hTopK;
890
- hTopPChanged: ChangeHandlerType<string> = this.hHandlers.hTopP;
264
+ ### Route Type Safety
891
265
 
892
- userImageChanged(oldVal: string, newVal: string) {
893
- if (newVal && oldVal !== newVal) {
894
- this.userImage = newVal;
895
- }
896
- }
266
+ If you use `IBackendRoute`/`HTTPRoutes` for your backend route definitions, you get type safety and autocompletion for all API calls.
897
267
 
898
- initialsChanged(oldVal: string, newVal: string) {
899
- if (newVal && oldVal !== newVal) {
900
- this.initials = newVal;
901
- }
902
- }
268
+ ## Example: WebChat Component
903
269
 
904
- convoIdChanged(oldVal: string, newVal: string) {
905
- if (newVal && oldVal !== newVal) {
906
- console.log(this.convoId);
907
- this.convoId = newVal;
908
- }
909
- }
910
- }
270
+ See the WebChat component for a practical example of APIService and RWSView usage.
911
271
 
912
- WebChat.defineComponent();
272
+ ## Other configs
913
273
 
274
+ See the original README for example `tsconfig.json` and webpack config.
914
275
 
915
- export { WebChat, IContext };
276
+ ## Plugin System
916
277
 
917
- ```
278
+ The plugin system allows you to extend the client with additional features. For example, you can add routing with `@rws-framework/browser-router` or websockets with `@rws-framework/nest-interconnectors`.
918
279
 
919
- ### Controller route
280
+ ## Nest Interconnectors
920
281
 
921
- The route ApiService.back.get|post|put|delete methods can be found in backend controllers:
282
+ The `@rws-framework/nest-interconnectors` package provides seamless integration with NestJS-based backend websockets and real-time features. You can add the plugin as follows:
922
283
 
923
284
  ```typescript
924
- @Route('talk:models:prompt', 'POST')
925
- public async modelTalkAction(params: IRequestParams): Promise<ITalkApiResponse>
926
- {
927
- // (...)
928
- }
929
- ```
930
-
931
- and src/config/config
285
+ import { RWSWebsocketsPlugin, WSOptions } from '@rws-framework/nest-interconnectors';
932
286
 
933
- ```typescript
934
- const http_routes = [
935
- {
936
- prefix: '/prefix',
937
- routes: [
938
- {
939
- name: 'action:route:name',
940
- path: '/path/to/action'
941
- },
942
- {
943
- name: 'action:route:name',
944
- path: '/path/to/action'
945
- }
946
- ]
947
- },
948
- {
949
- name: 'home:index',
950
- path: '/*', //if no routes detected pass request to frontend
951
- noParams: true, //do not read params from the request leave it to the front
952
- },
953
- ]
287
+ theClient.addPlugin<WSOptions>(RWSWebsocketsPlugin, {
288
+ enabled: true,
289
+ auto_notify: true
290
+ });
954
291
  ```
955
292
 
956
- ### Socket route
293
+ This enables real-time communication and event-driven features between your RWS frontend and a NestJS backend.
957
294
 
958
- Socket route from
295
+ ## Styles Injection
959
296
 
960
- ```typescript
961
- WSService.sendMessage<PayloadType>('send_msg', {
962
- modelId: this.chosenModel.modelId,
963
- prompt: msg.content
964
- });
965
-
966
- ```
967
-
968
- are defined in backend/src/config/config
297
+ RWS supports advanced styles injection for components. You can inject global or component-specific stylesheets using the static method:
969
298
 
970
299
  ```typescript
971
- const ws_routes = {
972
- 'send_msg' : ChatSocket,
973
- 'process_book' : TrainSocket,
974
- }
975
- ```
976
-
977
- ## Other configs
978
-
979
- ### example tsconfig.json
980
-
981
- ```json
982
- {
983
- "compilerOptions": {
984
- "baseUrl": "../",
985
- "experimentalDecorators": true,
986
- "emitDecoratorMetadata": true,
987
- "target": "ES2018",
988
- "module": "es2022",
989
- "moduleResolution": "node",
990
- "strict": true,
991
- "esModuleInterop": true,
992
- "sourceMap": true,
993
- "outDir": "dist",
994
- "strictNullChecks": false,
995
- "allowSyntheticDefaultImports": true,
996
- "lib": ["DOM", "ESNext", "WebWorker"],
997
- "paths": {
998
- }
999
- },
1000
- "include": [
1001
- "src",
1002
- "../node_modules/@rws-framework/client/declarations.d.ts", //TEMPORARILY NEEDED TO WORK
1003
- ],
1004
- "exclude": [
1005
- "../node_modules/@rws-framework/client/src/tests"
1006
- ]
1007
- }
1008
- ```
1009
-
1010
- **Remember to have lib field set in tsconfig.json**
1011
-
1012
- ```json
1013
- {
1014
- "lib": ["DOM", "ESNext"]
1015
- }
300
+ RWSViewComponent.injectStyles(["/css/global.css", "/css/theme.css"]);
1016
301
  ```
1017
302
 
1018
- ## Plugin system
303
+ - Styles can be injected in `adopted`, `legacy`, or `both` modes (default is `adopted`).
304
+ - Styles are cached in IndexedDB for performance and can be hot-reloaded.
305
+ - Each component can also inject its own styles via the `injectStyles` method or by specifying styles in the component definition.
1019
306
 
1020
- [PLUGIN SYSTEM README](https://github.com/papablack/rws-client/blob/master/PLUGINS.md)
307
+ This allows for efficient, encapsulated, and dynamic styling of your RWS components, supporting both modern and legacy browsers.
1021
308
 
1022
309
  ## Links
1023
- - 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 )
1024
- - https://www.webcomponents.org (open-source WebComponents repository)
310
+ - [RWSDocs instructions](../../.github/instructions/RWSDocs.instructions.md)
311
+ - [FAST documentation](https://www.fast.design/docs/fast-element/getting-started)
312
+ - [WebComponents.org](https://www.webcomponents.org)