@qelos/plugin-play 2.0.22

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.
Files changed (58) hide show
  1. package/.nvmrc +1 -0
  2. package/LICENSE +21 -0
  3. package/create-cert.js +6 -0
  4. package/dist/examples/basic-demo.d.ts +1 -0
  5. package/dist/examples/basic-demo.js +38 -0
  6. package/dist/examples/basic-demo.js.map +1 -0
  7. package/dist/examples/stocks/server.d.ts +1 -0
  8. package/dist/examples/stocks/server.js +47 -0
  9. package/dist/examples/stocks/server.js.map +1 -0
  10. package/dist/examples/todos/server.d.ts +1 -0
  11. package/dist/examples/todos/server.js +64 -0
  12. package/dist/examples/todos/server.js.map +1 -0
  13. package/dist/src/app.d.ts +16 -0
  14. package/dist/src/app.js +130 -0
  15. package/dist/src/app.js.map +1 -0
  16. package/dist/src/authentication.d.ts +20 -0
  17. package/dist/src/authentication.js +292 -0
  18. package/dist/src/authentication.js.map +1 -0
  19. package/dist/src/config.d.ts +17 -0
  20. package/dist/src/config.js +20 -0
  21. package/dist/src/config.js.map +1 -0
  22. package/dist/src/endpoints.d.ts +5 -0
  23. package/dist/src/endpoints.js +21 -0
  24. package/dist/src/endpoints.js.map +1 -0
  25. package/dist/src/handlers.d.ts +47 -0
  26. package/dist/src/handlers.js +32 -0
  27. package/dist/src/handlers.js.map +1 -0
  28. package/dist/src/index.d.ts +25 -0
  29. package/dist/src/index.js +15 -0
  30. package/dist/src/index.js.map +1 -0
  31. package/dist/src/manifest.d.ts +33 -0
  32. package/dist/src/manifest.js +24 -0
  33. package/dist/src/manifest.js.map +1 -0
  34. package/dist/src/sdk.d.ts +5 -0
  35. package/dist/src/sdk.js +49 -0
  36. package/dist/src/sdk.js.map +1 -0
  37. package/examples/basic-demo.ts +43 -0
  38. package/examples/stocks/public/assets/AMZN.png +0 -0
  39. package/examples/stocks/public/assets/GOOGL.png +0 -0
  40. package/examples/stocks/public/assets/META.png +0 -0
  41. package/examples/stocks/public/assets/MSFT.png +0 -0
  42. package/examples/stocks/public/future-client-sdk.js +30 -0
  43. package/examples/stocks/public/index.html +29 -0
  44. package/examples/stocks/public/main.js +56 -0
  45. package/examples/stocks/server.ts +51 -0
  46. package/examples/todos/public/index.html +13 -0
  47. package/examples/todos/public/main.js +70 -0
  48. package/examples/todos/server.ts +66 -0
  49. package/package.json +30 -0
  50. package/src/app.ts +152 -0
  51. package/src/authentication.ts +321 -0
  52. package/src/config.ts +24 -0
  53. package/src/endpoints.ts +24 -0
  54. package/src/handlers.ts +52 -0
  55. package/src/index.ts +36 -0
  56. package/src/manifest.ts +31 -0
  57. package/src/sdk.ts +53 -0
  58. package/tsconfig.json +16 -0
@@ -0,0 +1,43 @@
1
+ import {start, registerToHook, addMicroFrontend, getSdkForTenant} from '../src';
2
+
3
+ registerToHook({source: 'content', path: 'my-hook'}, async (request) => {
4
+ console.log('yay')
5
+ console.log(request.body);
6
+ const sdk = await getSdkForTenant(request.tenantPayload);
7
+
8
+ console.log(await sdk.blocks.getList());
9
+
10
+ return {message: 'king'};
11
+ });
12
+
13
+ addMicroFrontend({
14
+ name: 'league',
15
+ url: 'https://www.monkeyleague.io/#team',
16
+ description: 'google it',
17
+ route: {
18
+ name: 'custom-demo',
19
+ path: 'league',
20
+ navBarPosition: 'top',
21
+ },
22
+ });
23
+
24
+ addMicroFrontend({
25
+ name: 'David',
26
+ url: 'https://www.davidlevy.co.il/wallak/vue-react-rectivity',
27
+ description: 'tweet it',
28
+ route: {
29
+ name: 'davidlevy',
30
+ path: 'davidlevy',
31
+ navBarPosition: 'top',
32
+ },
33
+ });
34
+
35
+ start({
36
+ config: {
37
+ accessTokenSecret: 'demo-secret',
38
+ refreshTokenSecret: 'refresh-token-secret',
39
+ greenpressUrl: 'http://localhost:3000',
40
+ greenpressUsername: 'test@test.com',
41
+ greenpressPassword: 'admin',
42
+ },
43
+ });
@@ -0,0 +1,30 @@
1
+ export const code = (new URL(location.href)).searchParams.get('code');
2
+
3
+ export async function unAuthorize() {
4
+ await fetch('/api/un-authorize', {
5
+ method: 'post',
6
+ headers: {
7
+ code
8
+ },
9
+ })
10
+ }
11
+
12
+ export async function authorize() {
13
+ const returnUrl = location.href.replace(location.search, '');
14
+ const token = (new URL(location.href)).searchParams.get('token');
15
+
16
+ const res = await fetch('/api/authorize', {
17
+ method: 'post',
18
+ headers: {
19
+ 'content-type': 'application/json',
20
+ },
21
+ body: JSON.stringify({
22
+ returnUrl,
23
+ token
24
+ })
25
+ })
26
+
27
+ window.addEventListener('beforeunload', unAuthorize);
28
+
29
+ return res.json();
30
+ }
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Stocks</title>
6
+ <script type="module" src="main.js"></script>
7
+ <style>
8
+ table, iframe {width: 100%;}
9
+ td {width: 50%;text-align: center;padding: 10px;}
10
+ img {max-width: 100%;}
11
+ input {padding: 10px;border-radius: 5px;border: 1px solid #eee;}
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <h1>Stocks!</h1>
16
+ <div id="app">
17
+ <table>
18
+ <tr>
19
+ <th><input class="stock-0-input"></th>
20
+ <th><input class="stock-1-input"></th>
21
+ </tr>
22
+ <tr>
23
+ <td class="stock-0-view"></td>
24
+ <td class="stock-1-view"></td>
25
+ </tr>
26
+ </table>
27
+ </div>
28
+ </body>
29
+ </html>
@@ -0,0 +1,56 @@
1
+ import {authorize, code} from ''
2
+
3
+ const {user, tenant} = await authorize()
4
+
5
+ console.log(user, tenant)
6
+
7
+ const inputs = [
8
+ document.querySelector('.stock-0-input'),
9
+ document.querySelector('.stock-1-input'),
10
+ ];
11
+
12
+ const viewers = [
13
+ document.querySelector('.stock-0-view'),
14
+ document.querySelector('.stock-1-view'),
15
+ ]
16
+
17
+ async function fetchStocks() {
18
+ const res = await fetch('/api/get', {
19
+ headers: {
20
+ code
21
+ }
22
+ });
23
+
24
+ const stocks = await res.json()
25
+
26
+ stocks.forEach((stock, i) => {
27
+ inputs[i].value = stock;
28
+ viewers[i].innerHTML = `<img src="/assets/${stock}.png">`
29
+ })
30
+ }
31
+
32
+ async function updateStocks() {
33
+ const res = await fetch('/api/set', {
34
+ method: 'post',
35
+ headers: {
36
+ 'content-type': 'application/json',
37
+ code
38
+ },
39
+ body: JSON.stringify(inputs.map(input => input.value))
40
+ });
41
+
42
+ const stocks = await res.json()
43
+
44
+ stocks.forEach((stock, i) => {
45
+ viewers[i].innerHTML = `<img src="/assets/${stock}.png">`
46
+ })
47
+ }
48
+
49
+ inputs.forEach(input => {
50
+ input.addEventListener('change', () => {
51
+ updateStocks();
52
+ })
53
+ })
54
+
55
+
56
+ await fetchStocks();
@@ -0,0 +1,51 @@
1
+ import {start, addMicroFrontend, addEndpoint} from '../../src';
2
+ import {join} from 'path';
3
+
4
+ let stocks = ['GOOGL', 'META']
5
+
6
+ addMicroFrontend({
7
+ name: 'Stocks',
8
+ url: 'index.html',
9
+ description: 'My Stocks',
10
+ route: {
11
+ name: 'Stocks',
12
+ path: 'stocks',
13
+ navBarPosition: 'top',
14
+ },
15
+ });
16
+
17
+ addEndpoint('/api/get', {
18
+ async handler(req) {
19
+ return stocks
20
+ }
21
+ })
22
+
23
+ addEndpoint('/api/set', {
24
+ method: 'POST',
25
+ async handler(req) {
26
+ const body: any = req.body || stocks;
27
+ if(body instanceof Array) {
28
+ stocks = body;
29
+ }
30
+ return stocks;
31
+ }
32
+ })
33
+
34
+ start({
35
+ manifest: {
36
+ name: '@examples/stocks',
37
+ apiPath: '@examples:stocks'
38
+ },
39
+ config: {
40
+ port: '1087',
41
+ accessTokenSecret: 'demo-secret',
42
+ refreshTokenSecret: 'refresh-token-secret',
43
+ greenpressUrl: 'http://localhost:3000',
44
+ greenpressUsername: 'test@test.com',
45
+ greenpressPassword: 'admin',
46
+ staticFrontend: {
47
+ root: join(__dirname, 'public'),
48
+ prefix: '/'
49
+ }
50
+ },
51
+ });
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>TODOS</title>
6
+ <script type="module" src="main.js"></script>
7
+ <style>.box {border-radius: 5px; background-color: #fff4c2; padding: 10px; margin: 10px;}</style>
8
+ </head>
9
+ <body>
10
+ <h1>Todos!</h1>
11
+ <div id="app"></div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,70 @@
1
+ console.log(location);
2
+ const code = (new URL(location.href)).searchParams.get('code');
3
+
4
+
5
+ async function authorize() {
6
+ const returnUrl = location.href.replace(location.search, '');
7
+ const token = (new URL(location.href)).searchParams.get('token');
8
+
9
+ await fetch('/api/authorize', {
10
+ method: 'post',
11
+ headers: {
12
+ 'content-type': 'application/json',
13
+ },
14
+ body: JSON.stringify({
15
+ returnUrl,
16
+ token
17
+ })
18
+ })
19
+ }
20
+
21
+ async function fetchTodos() {
22
+ const res = await fetch('/api/todos', {
23
+ headers: {
24
+ code
25
+ }
26
+ });
27
+
28
+ const todos = await res.json()
29
+
30
+ document.querySelector('#app').innerHTML = `<add-todo>ADD</add-todo>` + todos.map(todo => {
31
+ return `<div class="box">
32
+ <h3>${todo.name}</h3>
33
+ <div>${todo.content}</div>
34
+ </div>`
35
+ }).join('')
36
+ }
37
+
38
+ class AddTodoButton extends HTMLElement {
39
+ async add() {
40
+ const name = prompt('choose a name');
41
+ const content = prompt('add content');
42
+ if (!(name && content)) {
43
+ return;
44
+ }
45
+
46
+ await fetch('/api/add-todos', {
47
+ method: 'post',
48
+ headers: {
49
+ 'content-type': 'application/json',
50
+ code
51
+ },
52
+ body: JSON.stringify({
53
+ name,
54
+ content
55
+ })
56
+ });
57
+
58
+ await fetchTodos();
59
+ }
60
+ constructor() {
61
+ super();
62
+ this.addEventListener('click', () => this.add());
63
+ }
64
+ }
65
+
66
+ customElements.define('add-todo', AddTodoButton)
67
+
68
+ await authorize()
69
+ await fetchTodos();
70
+
@@ -0,0 +1,66 @@
1
+ import {start, addMicroFrontend, addEndpoint, getSdkForTenant} from '../../src';
2
+ import {join} from 'path';
3
+
4
+ addMicroFrontend({
5
+ name: 'Todos',
6
+ url: 'index.html',
7
+ description: 'Todos List',
8
+ route: {
9
+ name: 'Todos',
10
+ path: 'todos',
11
+ navBarPosition: 'top',
12
+ },
13
+ });
14
+
15
+ addEndpoint('/api/todos', {
16
+ async handler(req) {
17
+ try {
18
+ const sdk = await getSdkForTenant(req.tenantPayload);
19
+ if (!sdk) {
20
+ return ['empty'];
21
+ }
22
+ return sdk.blocks.getList();
23
+ } catch {
24
+ //
25
+ }
26
+ return ['no access']
27
+ }
28
+ })
29
+
30
+ addEndpoint('/api/add-todos', {
31
+ method: 'POST',
32
+ async handler(req) {
33
+ const body: any = req.body || {}
34
+ try {
35
+ const sdk = await getSdkForTenant(req.tenantPayload);
36
+ if (!sdk) {
37
+ return ['empty'];
38
+ }
39
+ await sdk.blocks.create({
40
+ name: body.name,
41
+ content: body.content
42
+ })
43
+ } catch {
44
+ //
45
+ }
46
+ return ['no access']
47
+ }
48
+ })
49
+
50
+
51
+ start({
52
+ manifest: {
53
+ name: '@examples/todos'
54
+ },
55
+ config: {
56
+ accessTokenSecret: 'demo-secret',
57
+ refreshTokenSecret: 'refresh-token-secret',
58
+ greenpressUrl: 'http://localhost:3000',
59
+ greenpressUsername: 'test@test.com',
60
+ greenpressPassword: 'admin',
61
+ staticFrontend: {
62
+ root: join(__dirname, 'public'),
63
+ prefix: '/'
64
+ }
65
+ },
66
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@qelos/plugin-play",
3
+ "version": "2.0.22",
4
+ "description": "Play a plugin for Qelos",
5
+ "main": "./dist/index.js",
6
+ "scripts": {
7
+ "create-certs": "node create-cert.js",
8
+ "demo:basic": "npx tsx watch ./examples/basic-demo.ts",
9
+ "demo:todos": "npx tsx watch ./examples/todos/server.ts",
10
+ "demo:stocks": "npx tsx watch ./examples/stocks/server.ts",
11
+ "build": "tsc"
12
+ },
13
+ "author": "David Meir-Levy <davidmeirlevy@gmail.com>",
14
+ "license": "MIT",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "dependencies": {
19
+ "@fastify/cookie": "^8.3.0",
20
+ "@fastify/static": "^6.5.0",
21
+ "@qelos/sdk": "^2.0.22",
22
+ "@qelos/web-sdk": "^2.0.22",
23
+ "fastify": "^4.3.0",
24
+ "jsonwebtoken": "^8.5.1",
25
+ "undici": "^5.8.0"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^4.6.3"
29
+ }
30
+ }
package/src/app.ts ADDED
@@ -0,0 +1,152 @@
1
+ import {existsSync, readFileSync} from 'fs';
2
+ import {join} from 'path';
3
+ import fastify, {FastifyInstance} from 'fastify';
4
+ import {RouteHandlerMethod} from 'fastify/types/route';
5
+ import type {FastifyCookieOptions} from '@fastify/cookie'
6
+ import cookie from '@fastify/cookie'
7
+ import {manifest, ManifestOptions} from './manifest';
8
+ import config, {ConfigOptions, setConfig} from './config';
9
+ import {
10
+ getCallbackRoute,
11
+ getFrontendAuthorizationRoute, getFrontendUnAuthorizationRoute,
12
+ getRefreshTokenRoute,
13
+ getRegisterRoute,
14
+ verifyAccessToken, verifyCookieToken
15
+ } from './authentication';
16
+ import handlers from './handlers';
17
+ import {endpoints} from './endpoints';
18
+
19
+ const hooks = new Set<{ subscribedEvent, path, handler }>();
20
+
21
+ let app: FastifyInstance;
22
+
23
+ export async function start(options?: { manifest?: ManifestOptions, config?: ConfigOptions }) {
24
+ configure(options?.manifest || {}, options?.config || {});
25
+
26
+ const app = getApp();
27
+ try {
28
+ console.log('start application for environment: ', config.dev ? 'development' : 'production');
29
+ await app.listen({port: Number(config.port), host: config.host})
30
+ } catch (err) {
31
+ app.log.error(err);
32
+ process.exit(1);
33
+ }
34
+ }
35
+
36
+ export function configure(manifestOptions: ManifestOptions, appConfig: ConfigOptions) {
37
+ Object.assign(manifest, manifestOptions);
38
+ setConfig(appConfig);
39
+
40
+ if (config.dev && !manifest.appUrl) {
41
+ manifest.appUrl = 'https://' + config.host + ':' + config.port;
42
+ }
43
+ manifest.proxyUrl = new URL(manifest.proxyPath, manifest.appUrl).href;
44
+ }
45
+
46
+ export function getApp(): FastifyInstance {
47
+ return app || createApp();
48
+ }
49
+
50
+ export function registerToHook(hook: { source?: string, kind?: string, eventName?: string, path?: string }, handler: RouteHandlerMethod) {
51
+ hook.path = hook.path || btoa(JSON.stringify(hook));
52
+ const subscribedEvent = {
53
+ source: hook.source || '*',
54
+ kind: hook.kind || '*',
55
+ eventName: hook.eventName || '*',
56
+ };
57
+ hooks.add({
58
+ subscribedEvent,
59
+ path: hook.path,
60
+ handler
61
+ });
62
+ manifest.subscribedEvents.push(subscribedEvent);
63
+ }
64
+
65
+ function createApp(): FastifyInstance {
66
+ app = fastify( {
67
+ logger: !!config.dev,
68
+ https: {
69
+ cert: readFileSync(join(process.cwd(), 'cert/cert.pem')).toString(),
70
+ key: readFileSync(join(process.cwd(), 'cert/key.pem')).toString(),
71
+ }
72
+ });
73
+
74
+ app.addContentTypeParser('application/json', {parseAs: 'string'}, function (req, body, done) {
75
+ try {
76
+ const bodyStr = body.toString() as string;
77
+ done(null, bodyStr ? JSON.parse(bodyStr) : {})
78
+ } catch (err) {
79
+ err.statusCode = 400
80
+ done(err, undefined)
81
+ }
82
+ });
83
+
84
+ app.route(getRefreshTokenRoute());
85
+ app.route(getRegisterRoute());
86
+ app.route(getCallbackRoute());
87
+ playManifest();
88
+ playHooks();
89
+ playEndpoints();
90
+ playFrontend();
91
+
92
+ if (!(config.greenpressUrl || handlers.refreshToken.length)) {
93
+ throw new Error('you must provide either a refresh token handler or Greenpress application credentials');
94
+ }
95
+
96
+ return app;
97
+ }
98
+
99
+ function playManifest() {
100
+ getApp().route({
101
+ method: 'GET',
102
+ url: manifest.manifestUrl,
103
+ handler({headers, body}, res) {
104
+ setImmediate(() => handlers.manifest.forEach(cb => cb({headers, body})));
105
+ res.header('Access-Control-Allow-Origin', '*');
106
+ return manifest;
107
+ }
108
+ })
109
+ console.log('manifest URL: ' + new URL(manifest.manifestUrl, manifest.appUrl).href)
110
+ }
111
+
112
+ function playHooks() {
113
+ hooks.forEach(hook => {
114
+ const routePath = join('/api/hooks', hook.path);
115
+ hook.subscribedEvent.hookUrl = new URL(routePath, manifest.appUrl).href;
116
+ getApp().route({
117
+ method: 'POST',
118
+ url: routePath,
119
+ preHandler: verifyAccessToken,
120
+ handler: hook.handler
121
+ })
122
+ console.log('subscribed event configured: ', routePath, hook.subscribedEvent);
123
+ })
124
+ }
125
+
126
+ function playFrontend() {
127
+ if (existsSync(config.staticFrontend.root)) {
128
+ getApp().register(require('@fastify/static'), config.staticFrontend);
129
+ }
130
+ }
131
+
132
+ function playEndpoints() {
133
+ if (!endpoints.size) {
134
+ return;
135
+ }
136
+ getApp().register(cookie, {
137
+ parseOptions: {
138
+ maxAge: 1000 * 60 * 30, // 30 MINUTES,
139
+ secure: true,
140
+ httpOnly: true,
141
+ sameSite: config.dev ? 'none' : true
142
+ }
143
+ } as FastifyCookieOptions);
144
+ getApp().route(getFrontendAuthorizationRoute());
145
+ getApp().route(getFrontendUnAuthorizationRoute());
146
+ endpoints.forEach((routeOptions, path) => {
147
+ getApp().route({
148
+ ...routeOptions,
149
+ preHandler: path.startsWith(manifest.apiPath) ? verifyAccessToken : verifyCookieToken
150
+ });
151
+ })
152
+ }