Skip to content

Commit

Permalink
Merge branch 'master' into oserver-instance
Browse files Browse the repository at this point in the history
  • Loading branch information
Hyperkid123 authored Jul 11, 2023
2 parents 134db1d + 681ed1a commit 6a68703
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 0 deletions.
12 changes: 12 additions & 0 deletions config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ const commonConfig = ({ dev }) => {
proxyVerbose: true,
isChrome: true,
routes: {
...(process.env.CHROME_SERVICE && {
// web sockets
'/wss/chrome-service/': {
target: `ws://localhost:${process.env.CHROME_SERVICE}`,
// To upgrade the connection
ws: true,
},
// REST API
'/api/chrome-service/v1/': {
host: `http://localhost:${process.env.CHROME_SERVICE}`,
},
}),
...(process.env.CONFIG_PORT && {
'/beta/config': {
host: `http://localhost:${process.env.CONFIG_PORT}`,
Expand Down
87 changes: 87 additions & 0 deletions docs/localWSDevelopment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Local WS development

## Prerequisites

1. Have a go language setup. You can follow the [gmv guide](https://github.com/moovweb/gvm#installing).
2. Have a podman installed. [Getting started guide](https://podman.io/get-started)
3. Have the [chrome-service-backend](https://github.com/RedHatInsights/chrome-service-backend) checkout locally.
4. Make sure you terminal supports the [Makefile](https://makefiletutorial.com/) utility.

## Setting up the development environment

### Chrome service backend

The chrome service backend is the bridge between kafka and the browser client. It exposes a WS endpoint that allows the browser to connect to the service.

> **__Note__** these steps start only the minimal required infrastructure.
To enable it for local development with chrome UI follow these steps:

1. Make sure you are on the `main` branch or have created new branch fro the latest `main`.
2. <a name="bypass"></a>If the WS feature is hidden behind a feature flag, and you don't want to go through the FF setup, [bypass this condition](https://github.com/RedHatInsights/chrome-service-backend/blob/main/main.go#L61) by adding `true || ..` to the condition in your local repository.
3. Start the kafka container by running `make kafka`.
4. Start the chrome service by running `go run .` in the repo root.
5. Run a `go run cmd/kafka/testMessage.go` to test the connection. You should see a log in the terminal from where the go server was started.

#### The `make kafka` command failed.

It is possible that you have services running on your machine that occupy either the kafka or postgres ports. To change the default values, open the `local/kafka-compose.yaml` and change the postgres or kafka (or both) port bindings:

```diff
diff --git a/local/kafka-compose.yaml b/local/kafka-compose.yaml
index f8f3451..60fc9cd 100644
--- a/local/kafka-compose.yaml
+++ b/local/kafka-compose.yaml
@@ -8,7 +8,7 @@ services:
- POSTGRES_USER=chrome
- POSTGRES_PASSWORD=chrome
ports:
- - "5432:5432"
+ - "5555:5432"
volumes:
- db:/var/lib/postgresql/data

@@ -17,7 +17,7 @@ services:
hostname: zoo1
container_name: zoo1
ports:
- - "2181:2181"
+ - "8888:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_SERVER_ID: 1

```

#### The `go run cmd/kafka/testMessage.go`

The command will not work if the main server is not running, or the ws consumer did not start. Ensure you either have feature flags setup or you have bypassed the condition listed in the [step 2](#bypass).


### Chrome frontend

1. ensure you are on the latest version for chrome `master` branch. The support for WS client was added in [#2525](https://github.com/RedHatInsights/insights-chrome/pull/2525).
2. Once your chrome service backend is running, start the chrome dev server with the chrome service config using this command: `CHROME_SERVICE=8000 yarn dev`.
3. The WS client connection is hidden behind a `platform.chrome.notifications-drawer` feature flag. If its not available in your current environment (stage or prod or EE), bypass the feature flag condition in the `src/hooks/useChromeServiceEvents.ts` file.
4. OPen the browser, open the browser console, emit a custom message from the chrome service terminal using `go run cmd/kafka/testMessage.go`.
5. In the network tab of your browser console, filter only to show `ws` communication, click on related ws connection (there will be a couple for webpack dev server, ignore these) and observer the messages being pushed down to the browser.

#### Sample WS message payload

```js
{
// the actual payload consumed by chrome
data: {
description: "Some longer description",
title: "New notification"
},
// cloud events sub protocol metadata
datacontenttype: "application/json",
id: "test-message",
source: "https://whatever.service.com",
specversion: "1.0.2",
time: "2023-05-23T11:54:03.879689005+02:00",
// a type field used to identify message purpose
type: "notifications.drawer"
}
```
4 changes: 4 additions & 0 deletions src/components/RootApp/ScalprumRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import useBundleVisitDetection from '../../hooks/useBundleVisitDetection';
import chromeApiWrapper from './chromeApiWrapper';
import { ITLess } from '../../utils/common';
import InternalChromeContext from '../../utils/internalChromeContext';
import useChromeServiceEvents from '../../hooks/useChromeServiceEvents';

const ProductSelection = lazy(() => import('../Stratosphere/ProductSelection'));

Expand All @@ -52,6 +53,9 @@ const ScalprumRoot = memo(
const store = useStore<ReduxState>();
const modulesConfig = useSelector(({ chrome: { modules } }: ReduxState) => modules);

// initialize WS event handling
useChromeServiceEvents();

const { setActiveTopic } = useHelpTopicManager(helpTopicsAPI);

function isStringArray(arr: EnableTopicsArgs): arr is string[] {
Expand Down
89 changes: 89 additions & 0 deletions src/hooks/useChromeServiceEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { useFlag } from '@unleash/proxy-client-react';

import { getEncodedToken, setCookie } from '../jwt/jwt';

const NOTIFICATION_DRAWER = 'notifications.drawer';
const SAMPLE_EVENT = 'sample.type';

const ALL_TYPES = [NOTIFICATION_DRAWER, SAMPLE_EVENT] as const;
type EventTypes = typeof ALL_TYPES[number];

type SamplePayload = {
foo: string;
};

type NotificationPayload = {
title: string;
description: string;
};

type Payload = NotificationPayload | SamplePayload;
interface GenericEvent<T extends Payload = Payload> {
type: EventTypes;
data: T;
}

function isGenericEvent(event: unknown): event is GenericEvent {
return typeof event === 'object' && event !== null && ALL_TYPES.includes((event as Record<string, never>).type);
}

const useChromeServiceEvents = () => {
const connection = useRef<WebSocket | undefined>();
const dispatch = useDispatch();
const isNotificationsEnabled = useFlag('platform.chrome.notifications-drawer');

const handlerMap: { [key in EventTypes]: (payload: Payload) => void } = useMemo(
() => ({
[NOTIFICATION_DRAWER]: (data: Payload) => dispatch({ type: 'foo', payload: data }),
[SAMPLE_EVENT]: (data: Payload) => console.log('Received sample payload', data),
}),
[]
);

function handleEvent(type: EventTypes, data: Payload): void {
handlerMap[type](data);
}

const createConnection = async () => {
const token = getEncodedToken();
if (token) {
const socketUrl = `${document.location.origin.replace(/^.+:\/\//, 'wss://')}/wss/chrome-service/v1/ws`;
// ensure the cookie exists before we try to establish connection
await setCookie(token);

// create WS URL from current origin
// ensure to use the cloud events sub protocol
const socket = new WebSocket(socketUrl, 'cloudevents.json');
connection.current = socket;

socket.onmessage = (event) => {
const { data } = event;
try {
const payload = JSON.parse(data);
if (isGenericEvent(payload)) {
handleEvent(payload.type, payload.data);
} else {
throw new Error(`Unable to handle event type: ${event.type}. The payload does not have required shape! ${event}`);
}
} catch (error) {
console.error('Handler failed when processing WS payload: ', data, error);
}
};
}
};

useEffect(() => {
try {
// create only one connection and only feature is enabled
if (isNotificationsEnabled && !connection.current) {
createConnection();
}
} catch (error) {
console.error('Unable to establish WS connection');
}
}, [isNotificationsEnabled]);
};

export default useChromeServiceEvents;
2 changes: 2 additions & 0 deletions src/jwt/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ export async function setCookie(token?: string) {
if (cookieName) {
setCookieWrapper(`${cookieName}=${tok};` + `path=/wss;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`);
setCookieWrapper(`${cookieName}=${tok};` + `path=/ws;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`);
setCookieWrapper(`${cookieName}=${tok};` + `path=wss://;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`);
setCookieWrapper(`${cookieName}=${tok};` + `path=ws://;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`);
setCookieWrapper(`${cookieName}=${tok};` + `path=/api/tasks/v1;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`);
setCookieWrapper(`${cookieName}=${tok};` + `path=/api/automation-hub;` + `secure=true;` + `expires=${getCookieExpires(decodeToken(tok).exp)}`);
setCookieWrapper(`${cookieName}=${tok};` + `path=/api/remediations/v1;` + `secure=true;` + `expires=${getCookieExpires(tokExpires)}`);
Expand Down

0 comments on commit 6a68703

Please sign in to comment.