Product
Introducing SSO
Streamline your login process and enhance security by enabling Single Sign-On (SSO) on the Socket platform, now available for all customers on the Enterprise plan, supporting 20+ identity providers.
@rws-framework/client
Advanced tools
Readme
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.
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.
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.
from your project dir do:
yarn
Initiate cfg files and webpack build:
rws-client init
to install once and then to build after preparing components:
yarn build
or to watch for dev
yarn watch
or to just start server
yarn server
then start engine in the site javascript (can be inline):
window.RWS.client.start(CFG); // it is async function
or for initial setup then start on certain event (example)
window.RWS.client.setup(CFG).then(() => { // it is async function
$.on('loaded', function(data){
const optionalNewCfg = { backendRoutes: data.backendRoutes };
window.RWSClient.start(optionalNewCfg).then();
})
});
const _DEFAULT_CONFIG_VARS = {
//Build configs
dev: false,
hot: false,
report: false,
publicDir: './public',
publicIndex: 'index.html',
outputFileName: 'client.rws.js',
outputDir: process.cwd() + '/build',
//Frontend RWS client configs
backendUrl: null,
wsUrl: null,
partedDirUrlPrefix: '/lib/rws',
partedPrefix: 'rws',
pubUrlFilePrefix: '/',
//Universal configs
transports: ['websocket'],
parted: false,
}
The options description:
Option | Description | Default |
---|---|---|
backendUrl | Url for backend integration (API calls) | null |
wsUrl | Url for backend integration (Websocket calls) | null |
backendRoutes | Backend routes object imported from backend node for integration with API calls | null |
apiPrefix | Prefix for API calls | / |
routes | Routes for frontend routing | {} |
transports | Websockets transports method | ['websockets'] |
user | User object for backend auth / frontend data source | null |
ignoreRWSComponents | Do not declare base RWS components (uploader, progress) | false |
pubUrlFilePrefix | the url for accessing files from browser URL | / |
pubUrl | the url for accessing public dir from browser URL | / |
outputDir | build dir | ./build |
outputFileName | output file name | rws.client.js |
publicDir | public dir for HTML serving | ./public |
tsConfigPath | tsconfig.json path | ./tsconfig.njson |
entry | default TS entry for transpilation | ./src/index.ts |
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 |
partedPrefix | parted file prefix ([prefix].[cmp name].js) | rws |
partedDirUrlPrefix | URL for generated js parted files directory | / |
copyAssets | An option for defining structure that will be copied after build | {} |
copyAssets example
"copyAssets": {
"./public/js/": [ // target directory
"./build/", // copy this directory to target
"./src/styles/compiled/main.css" //copy this file to target
]
}
interface IRWSConfig {
defaultLayout?: typeof RWSViewComponent
backendUrl?: string
wsUrl?: string
backendRoutes?: any[]
apiPrefix?: string
routes?: IFrontRoutes
transports?: string[]
user?: any
ignoreRWSComponents?: boolean
pubUrl?: string
pubUrlFilePrefix?: string
partedDirUrlPrefix?: string
dontPushToSW?: boolean
parted?: boolean
partedFileDir?: string
partedPrefix?: string
routing_enabled?: boolean
_noLoad?: boolean
}
const path = require('path');
const RWSWebpackWrapper = require('@rws-framework/client/rws.webpack.config');
const executionDir = process.cwd();
module.exports = RWSWebpackWrapper({
tsConfigPath: executionDir + '/tsconfig.json',
entry: `${executionDir}/src/index.ts`,
publicDir: path.resolve(executionDir, 'public'),
outputDir: path.resolve(executionDir, 'build'),
outputFileName: 'jtrainer.client.js'
});
RWS.client
is the heart of the framework, managing configuration and initialization. It sets up routes, backend connections, and other essential framework services.
RoutingService
handles the navigation and routing within your application. It ensures that URL changes reflect the correct component rendering.
Depreciation Notice
RoutingService will be moved to @rws-framework/browser-router near future
WSService
handles Websockets messenging to the backend.
Depreciation Notice WSService will be moved to @rws-framework/nest-interconnectors in near future
APIService
handles API requests to the backend.
Implementing the Framework
Main File:
The main file (index.ts
) is where you initialize the RWSClient. Here, you configure your routes, backend routes, and component initializations.
Following is example of full usage of the framework
async function initializeApp() {
const theClient = RWSContainer().get(RWSClient);
theClient.addRoutes(frontendRoutes);
theClient.setBackendRoutes(backendRoutes());
theClient.enableRouting();
theClient.onInit(async () => {
// For single file output:
initComponents(theClient.appConfig.get('parted')); // start components for monolith mode
theClient.defineComponents(); // start RWS conponents
//custom outside components registering
provideFASTDesignSystem()
.register(fastButton())
.register(fastTab())
.register(fastSlider())
.register(fastSelect())
.register(fastDivider())
.register(fastMenu())
.register(fastMenuItem())
;
// Service worker code
// const swFilePath: string = `${theClient.appConfig.get('pubUrl')}/service_worker.js`;
// await theClient.swService.registerServiceWorker();
//if(theClient.getUser()){
// theClient.pushUserToServiceWorker({...theClient.getUser(), instructor: false});
//}
});
theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void, notifierOptions: any = {}) => {
switch(uiType){
case 'notification':
let notifType = 'success';
if(logType === 'error'){
notifType = 'error';
}
if(logType === 'warning'){
notifType = 'warning';
}
return alertify.notify(message, notifType, 5, onConfirm);
case 'alert':
const alertObj = alertify.alert('Junction AI Notification', message, onConfirm);
Object.keys(notifierOptions).forEach(key => {
const optionValue = notifierOptions[key];
if(key === 'width'){
alertObj.elements.dialog.style = `max-width: ${optionValue};`;
return;
}
alertObj.set(key, optionValue);
});
alertObj.show();
return alertObj;
case 'silent':
if(logType == 'warning'){
console.warn(message);
}else if(logType == 'error'){
console.error(message);
}else{
console.log(message);
}
return;
}
});
theClient.assignClientToBrowser();
}
initializeApp().catch(console.error);
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.
This should be conditioned not to execute imported code when using parted mode.
component-dir/
component.ts
template.html
styles/
layout.scss
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
<div class="convo-area-wrap">
<header>
<div class="header-inner"></div>
${T.when(x => x.noChoose === 'false', (item, index) => T.html`<div>
<chat-convo-models :chosenModel="${x => x.chosenModel}"></chat-convo-models>
</div>`)}
<div>
<h2>${ x => x.chatContext ? x.chatContext.label : 'loading...' }</h2>
<h3><strong>${ x => x.messageList.length }</strong> messages in total</h3>
</div>
<fast-divider></fast-divider>
</header>
<section>
<div class="scroll-area">
<div class="scroll-content">
${T.repeat(x => x.messageList, (item, index) => T.html`
<chat-convo-message :contentReturn="${item => item}" :item="${item => item}"/>
`)}
${T.when(x => !x.messageList.length, (item, index) => T.html`
<p class="no-chat">No messages</p>
`)}
</div>
</div>
</section>
</div>
Only if parted mode is false.
import { ChatNav } from '../components/chat-nav/component';
import { DefaultLayout } from '../components/default-layout/component';
import { RWSIcon } from '../components/rws-icon/component';
import { LineSplitter } from '../components/line-splitter/component';
import { WebChat } from '../components/webchat/component';
export default (partedMode: boolean = false) => {
if(!partedMode){
WebChat;
LineSplitter;
DefaultLayout;
ChatNav;
RWSIcon;
}
};
Component needs to extend RWSViewComponent and use @RWSView decorator:
import { RWSViewComponent, RWSView, observable, attr } from '@rws-framework/client';
const options?: RWSDecoratorOptions;
@RWSView('tag-name', options)
class WebChat extends RWSViewComponent {
@attr tagAttr: string; //HTML tag attr
@ngAttr fromNgAttr: string; //HTML attr from angular template
@sanitizedAttr htmlAttr: string; //HTML attr that's sanitized with every val change
@observable someVar: any; //Var for templates/value change observation
}
The decorator options type:
interface RWSDecoratorOptions{
template?: string, //relative path to HTML template file (default: ./template.html)
styles?: string //relative path to SCSS file (./styles/layout.scss)
fastElementOptions?: any //the stuff you would insert into static definition in FASTElement class.
}
import { RWSViewComponent, RWSView } from 'rws-js-client';
@RWSView('your-tag');
class YourComponent extends RWSViewComponent {
someMethod(url: string): void
{
this.apiService.get(url);
}
}
Default services: https://github.com/papablack/rws-client/blob/7d16d9c6d83c81c9fe470eb0f507756bc6c71b35/src/components/_component.ts#L58
import {
RWSView, RWSViewComponent, RWSInject,
DateService, DateServiceInstance
} from 'rws-js-client';
import DateService, {DateServiceInstance} from '../../my-custom-services/DateService';
@RWSView('your-tag')
class YourComponent extends RWSViewComponent {
//usage in props:
private @RWSInject(ServiceFASTDIPointer) serviceProperty: ServiceClassType;
//usage in constructor:
constructor(
private @RWSInject(DateService) protected dateService: DateServiceInstance
) {
super();
}
someMethod(url: string): void
{
this.dateService.get(url);
}
}
Custom service needs to export .getSingleton() as default export and have service class exported as classic export for TS typing:
import { RWSService } from '@rws-framework/client';
class DateService extends RWSService {
//(...)
}
export default DateService.getSingleton(); // Fast DI service pointer (it points to instanced service in DI container)
export { DateService as DateServiceInstance }; // the custom service class type
if you are passing routes this is example routing file for frontend:
export default {
'/': renderRouteComponent('Home page', WebChat),
'/the/path': renderRouteComponent('Component title', ComponentClassName),
}
Router tag:
<section>
<rws-router></rws-router>
</section>
backendImports.ts
consolidates various backend interfaces, routes, and models, allowing for a synchronized frontend and backend from package https://github.com/papablack/rws
import IBook from '../../backend/src/models/interfaces/IBook';
import {
IBookInfo,
} from '../../backend/src/interfaces/IBookInfo';
import backendRoutes from '../../backend/src/routing/routes';
export {
IBook,
IBookInfo,
backendRoutes
}
usage:
import { backendRoutes} from '../../backendImport';
//index.ts
const theClient = new RWSClient();
theClient.setBackendRoutes(backendRoutes());
APIService
is used for making HTTP requests to the backend. It simplifies the process of interacting with your API endpoints.
after control method we have dynamic types those are: <ResponseType, PayloadType>
Example Usage by controller route
const apiPromise: Promise<ITalkApiResponse> = this.apiService.back.post<ITalkApiResponse, IApiTalkPayload>('talk:models:prompt', {
message: msg,
model: this.chosenModel,
});
Example Usage by url
const apiPromise: Promise<ITalkApiResponse> = this.apiService.post<ITalkApiResponse, IApiTalkPayload>('/api/path/to/action', {
message: msg,
model: this.chosenModel,
});
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. Usage
theClient.setNotifier((message: string, logType: NotifyLogType, uiType: NotifyUiType = 'notification', onConfirm: (params: any) => void) => {
// Implementation based on uiType
});
This function allows setting a custom notifier in the RWS Client. It handles the logic based on uiType
.
Alert, Notify, and Silent
Each method can be configured with a message
, logType
, and an optional onConfirm
callback function.
Note
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.
If you pass {serviceWorker: 'service_worker_class_path.ts'}
to RWS Webpack wrapper function param, the code will build ServiceWorker to pubDir.
example ServiceWorker class:
import SWService, { ServiceWorkerServiceInstance } from '@rws-framework/client/src/services/ServiceWorkerService'
import {TimeTracker} from '../services/TimeTrackerService';
import RWSServiceWorker from '@rws-framework/client/src/service_worker/src/_service_worker';
import { RWSWSService as WSService } from '@rws-framework/client/src/services/WSService'
declare const self: ServiceWorkerGlobalScope;
class MyServiceWorker extends RWSServiceWorker {
public tracker: { currentTracker: TimeTracker | null };
public trackersToSync: TimeTracker[];
protected regExTypes: { [key: string]: RegExp } = {
SOME_VIEW: new RegExp('.*:\\/\\/.*\\/#\\/([a-z0-9].*)\\/route\\/action$')
};
ignoredUrls = [
new RegExp('(.*(?=.[^.]*$).*)/#/login'),
new RegExp('(.*(?=.[^.]*$).*)/#/logout'),
];
constructor(){
super(self, RWSContainer());
}
checkForbidden(url: string): boolean {
if (!url) {
return true;
}
console.log('[SW] Check forbidden', url);
return this.ignoredUrls.some((item) => url.match(item));
}
isExtraType(id: string){
let result: string | null = null;
const _self = this;
Object.keys(this.regExTypes).forEach(function(key){
if(result === null && _self.regExTypes[key].exec(id) !== null){
result = key;
}
});
return result;
}
startServiceWorker(regExTypes: { [key: string]: RegExp }, forbiddenUrls: RegExp[]): JunctionServiceWorker
{
this.tracker = { currentTracker: null };
this.ignoredUrls = forbiddenUrls;
this.trackersToSync = [];
this.regExTypes = regExTypes;
return this;
}
async onInit(): Promise<void>
{
const _self: JunctionServiceWorker = this;
let THE_USER: IJunctionUser | null = null;
const toSync: TimeTracker[] = [];
let WS_URL: string | null;
console.log('Initiating ServiceWorker');
this.workerScope.addEventListener('message', (event: MSGEvent) => {
// console.log(event);
if(!event.data){
console.warn('[SW] Got empty message');
return;
}
if (event.data.command){
console.log('[SW] OP Message:', event.data.command);
switch (event.data.command) {
case 'SET_WS_URL':
WS_URL = event.data.params.url;
break;
case 'SET_USER':
if(!this.getUser()){
THE_USER = event.data.params;
this.setUser(THE_USER);
}
_self.checkWs(WS_URL, this.getUser());
break;
case 'START_TRACKING':
_self.checkWs(WS_URL, this.getUser());
if(!this.wsService.socket() && this.getUser()){
break;
}
_self.trackActivity(event.data.asset_type, event.data.params.page_location, event.data.params, toSync);
break;
case 'TRACKER_SAVED':
const { clientId, tracker } = event.data.params;
_self.sendMessageToClient(clientId, { message: 'TRACKER_SAVED_RESPONSE', data: tracker });
break;
}
}
});
}
async onActivate(): Promise<void>
{
console.log('Activated ServiceWorker');
this.startServiceWorker(this.regExTypes, this.ignoredUrls);
}
private checkWs(WS_URL: string, THE_USER: IJunctionUser): boolean
{
if(!this.wsService.socket() && WS_URL){
this.wsService.init(WS_URL, THE_USER);
return true;
}
return false;
};
}
MyServiceWorker.create();
We point to this file in webpack / .rws.json "service_worker" option
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.
import { RWSViewComponent, ApiService, NotifyService, RWSView, WSService } from '@rws-framework/client';
import { observable, css } from '@microsoft/fast-element';
import './children/convo-footer/component';
import WebChatEvents from './events';
import { IContext } from './children/left-bar/component';
import { IMessage } from '../chat-message/component';
import { ITalkApiResponse, BedrockBaseModel, IHyperParameter,
@RWSView('web-chat')
class WebChat extends RWSViewComponent {
static fileList: string[] = [
'svg/icon_talk_1.svg'
];
@observable messages: IMessage[] = [];
@observable hyperParameters: { key: string, value: any } | any = {};
@observable bookId: string = null;
@observable chapterNr: string = null;
@observable chosenModel: BedrockBaseModel = null;
@observable chatContext: IContext = { label: 'Book chat' };
@observable bookModel: IBook = null;
@observable minified: boolean = true;
@ngAttr custombookid: string = null;
@ngAttr customchapternr: string = null;
@observable customTemperature: number = 0.7;
@observable customTopK: number = 250;
@observable customMaxTokensToSample: number = 1024;
@observable customTopP: number = 0.7;
@ngAttr hTemperature?: string = '0.7';
@ngAttr hTopK?: string = '250';
@ngAttr hMaxTokensToSample?: string = '1024';
@ngAttr hTopP?: string = '0.7';
@observable convoId: string;
@observable wsId: string;
@ngAttr dev: PseudoBool = 'false';
@ngAttr opened: PseudoBool = 'false';
@ngAttr userImage: string | null = null;
@ngAttr initials: string | null = 'U';
handlers: (this: WebChat) => IWebChatHandlers = assignHandlers;
streamCall: (msg: IMessage) => Promise<void> = callStreamApi;
getDefaultHyperParams = getDefaultParams;
setHyperParam = setHyperParam;
public msgOptions: IConvoMsgOptions = {
headerEnabled: false,
dateEnabled: false
};
connectedCallback() {
super.connectedCallback();
if (this.routeParams?.dev || this.dev === 'true') {
this.dev = 'true';
} else {
this.dev = 'false';
}
this.checkForBookId();
this.checkForBookChapter();
this.chosenModel = ClaudeModel;
const provider = this.chosenModel?.providerName?.toLowerCase() || null;
const defParams = this.getDefaultHyperParams(provider);
const defaultParams: { [key: string]: any } = {};
Object.keys(defParams).forEach(paramKey => {
if (defParams[paramKey]) {
defaultParams[paramKey] = this.setHyperParam(paramKey, defParams[paramKey]);
}
});
this.hyperParameters = { ...defaultParams, ...this.hyperParameters };
this.wsId = uuid();
this.on<{ item: IMessage }>(WebChatEvents.message.send, (event: CustomEvent<{ item: IMessage }>) => {
this.streamCall(event.detail.item);
});
if (this.routeParams?.opened || this.opened === 'true') {
this.minified = false;
}
if (this.hTemperature) {
this.hHandlers.hTemperature(null, this.hTemperature);
}
if (this.hMaxTokensToSample) {
this.hHandlers.hMaxTokensToSample(null, this.hMaxTokensToSample);
}
if (this.hTopK) {
this.hHandlers.hTopK(null, this.hTopK);
}
if (this.hTopP) {
this.hHandlers.hTopP(null, this.hTopP);
}
}
checkForBookId() {
this.bookId = this.routeParams.bookId || this.custombookid || null;
if (this.bookId) {
this.apiService.back.get<IBook>('train:get:book', { routeParams: { bookId: this.bookId } }).then((data: IBook) => {
this.bookModel = data;
});
}
}
checkForBookChapter() {
this.chapterNr = this.routeParams.chapterNr || this.customchapternr || null;
}
custombookidChanged(oldVal: string, newVal: string) {
if (newVal) {
this.custombookid = newVal;
this.checkForBookId();
} else {
this.custombookid = null;
}
}
customchapternrChanged(oldVal: string, newVal: string) {
if (newVal) {
this.customchapternr = newVal;
this.checkForBookChapter();
} else {
this.customchapternr = null;
}
}
devChanged(oldVal: string, newVal: string) {
if (oldVal !== newVal) {
this.dev = newVal === 'true' ? 'true' : 'false';
}
}
hHandlers: IHyperHandler = getParamChangeHandlers.bind(this)();
hTemperatureChanged: ChangeHandlerType<string> = this.hHandlers.hTemperature;
hMaxTokensToSampleChanged: ChangeHandlerType<string> = this.hHandlers.hMaxTokensToSample;
hTopKChanged: ChangeHandlerType<string> = this.hHandlers.hTopK;
hTopPChanged: ChangeHandlerType<string> = this.hHandlers.hTopP;
userImageChanged(oldVal: string, newVal: string) {
if (newVal && oldVal !== newVal) {
this.userImage = newVal;
}
}
initialsChanged(oldVal: string, newVal: string) {
if (newVal && oldVal !== newVal) {
this.initials = newVal;
}
}
convoIdChanged(oldVal: string, newVal: string) {
if (newVal && oldVal !== newVal) {
console.log(this.convoId);
this.convoId = newVal;
}
}
}
WebChat.defineComponent();
export { WebChat, IContext };
The route ApiService.back.get|post|put|delete methods can be found in backend controllers:
@Route('talk:models:prompt', 'POST')
public async modelTalkAction(params: IRequestParams): Promise<ITalkApiResponse>
{
// (...)
}
and src/config/config
const http_routes = [
{
prefix: '/prefix',
routes: [
{
name: 'action:route:name',
path: '/path/to/action'
},
{
name: 'action:route:name',
path: '/path/to/action'
}
]
},
{
name: 'home:index',
path: '/*', //if no routes detected pass request to frontend
noParams: true, //do not read params from the request leave it to the front
},
]
Socket route from
WSService.sendMessage<PayloadType>('send_msg', {
modelId: this.chosenModel.modelId,
prompt: msg.content
});
are defined in backend/src/config/config
const ws_routes = {
'send_msg' : ChatSocket,
'process_book' : TrainSocket,
}
{
"compilerOptions": {
"baseUrl": "../",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2018",
"module": "es2022",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"outDir": "dist",
"strictNullChecks": false,
"allowSyntheticDefaultImports": true,
"lib": ["DOM", "ESNext", "WebWorker"],
"paths": {
}
},
"include": [
"src",
"../node_modules/@rws-framework/client/declarations.d.ts", //TEMPORARILY NEEDED TO WORK
],
"exclude": [
"../node_modules/@rws-framework/client/src/tests"
]
}
Remember to have lib field set in tsconfig.json
{
"lib": ["DOM", "ESNext"]
}
FAQs
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.
The npm package @rws-framework/client receives a total of 287 weekly downloads. As such, @rws-framework/client popularity was classified as not popular.
We found that @rws-framework/client demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Streamline your login process and enhance security by enabling Single Sign-On (SSO) on the Socket platform, now available for all customers on the Enterprise plan, supporting 20+ identity providers.
Security News
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
Security News
As cyber threats become more autonomous, AI-powered defenses are crucial for businesses to stay ahead of attackers who can exploit software vulnerabilities at scale.