@empyria/webui 0.1.2 → 0.1.3

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/app/consts.ts CHANGED
@@ -2,4 +2,4 @@ export const MIN_WINDOW_WIDTH: number = 1280
2
2
  export const MIN_WINDOW_HEIGHT: number = 700
3
3
  export const OPTIMAL_WIDTH: number = 1500
4
4
 
5
- export const AccessTokenCookieKey = 'cbh-access-token'
5
+ export const AccessTokenCookieKey = 'empyria-access-token'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empyria/webui",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Restate.dev helpers for the Empyria nanoservice framework",
5
5
  "license": "MIT",
6
6
  "author": "Imre Fazekas <imre.fazekas@icloud.com>",
@@ -24,6 +24,8 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@empyria/common": "0.1.3",
27
+ "@empyria/moleculer": "0.1.3",
28
+ "@empyria/restate": "0.1.6",
27
29
  "@iconify-json/lucide": "^1.2.126",
28
30
  "@iconify-json/simple-icons": "^1.2.93",
29
31
  "@pinia/nuxt": "^1.0.2",
package/server/env.js CHANGED
@@ -1,6 +1,20 @@
1
1
  import { createEnv, validate } from '@empyria/common'
2
2
 
3
- const schema = createEnv({})
3
+ const schema = createEnv({
4
+ /*
5
+ moleculer: {
6
+ transporterURL: env.NUXT_MOL_TRANSPORTER,
7
+ namespace: env.NUXT_MOL_NAMESPACE,
8
+ servicesRequired: env.NUXT_MOL_SERVICES_REQUIRED?.split(',') || [],
9
+ waitTimeout: parseInt(env.NUXT_MOL_WAIT_TIMEOUT || '30000', 10),
10
+ },
11
+
12
+ restate: {
13
+ url: env.NUXT_RESTATE_URL,
14
+ adminURL: env.NUXT_RESTATE_ADMIN_URL,
15
+ },
16
+ */
17
+ })
4
18
 
5
19
  export const env = validate(schema, { ...process.env }, { coerceTypes: true })
6
20
 
@@ -0,0 +1,139 @@
1
+ import type { ServiceBroker as MoleculerBroker } from 'moleculer'
2
+ import { randomUUID as uuid } from 'node:crypto'
3
+ import { connect } from '@empyria/moleculer/Connector.js'
4
+ import { getCookie } from 'h3'
5
+
6
+ import { AccessTokenCookieKey } from '../../app/consts'
7
+
8
+ import { settings } from '../env'
9
+
10
+ import {
11
+ BaseErrors,
12
+ EMPYRIA_FEDERATION_ID,
13
+ MOLECULER_SERVICE_ROLE,
14
+ moleculerMeta,
15
+ } from '@empyria/common'
16
+
17
+ const whitelistedActions = [
18
+ 'v1.Auth.registerPasskeyBegin',
19
+ 'v1.Auth.registerPasskeyFinish',
20
+ 'v1.Auth.loginPasskeyBegin',
21
+ 'v1.Auth.loginPasskeyFinish',
22
+ ]
23
+
24
+ const NUXT_NAME = 'Nuxt Gateway'
25
+ const NUXT_META = moleculerMeta({
26
+ actor: NUXT_NAME,
27
+ federation: EMPYRIA_FEDERATION_ID,
28
+ flowID: uuid(),
29
+ processID: uuid(),
30
+ role: MOLECULER_SERVICE_ROLE,
31
+ tokenKey: `${NUXT_NAME}`,
32
+ })
33
+
34
+ declare global {
35
+ var moleculer: {
36
+ broker: MoleculerBroker
37
+ call<T>(
38
+ actionName: string,
39
+ params?: any,
40
+ opts?: { timeout?: number; meta?: Record<string, any> },
41
+ ): Promise<T>
42
+ emit(eventName: string, payload?: any, groups?: string | string[]): Promise<void>
43
+ broadcast(eventName: string, payload?: any, groups?: string | string[]): Promise<void>
44
+ }
45
+ }
46
+
47
+ export default defineNitroPlugin(async () => {
48
+ const transporterURL = settings.moleculer.transporterURL
49
+ const namespace = settings.moleculer.namespace
50
+ const nodeID = `UI-${uuid()}`
51
+ const servicesRequired = settings.moleculer.servicesRequired
52
+ const waitTimeout = settings.moleculer.waitTimeout
53
+
54
+ let connector: Awaited<ReturnType<typeof connect>> | undefined
55
+
56
+ try {
57
+ // Wait for required services to be available (only in production)
58
+ connector = await connect({
59
+ namespace,
60
+ nodeID,
61
+ transporter: transporterURL,
62
+ logLevel: 'warn', // Keep it quiet in the UI
63
+ servicesRequired: settings.prod ? servicesRequired : [],
64
+ waitTimeout,
65
+ })
66
+
67
+ if (settings.prod) {
68
+ console.log('✅ Required services are available')
69
+ } else {
70
+ console.log(`ℹ️ Skipping service wait in development mode`)
71
+ console.log(`ℹ️ Set NODE_ENV=production to enable strict service checking`)
72
+ }
73
+ } catch (err) {
74
+ console.error('❌ Failed to initialize Moleculer:', err)
75
+ if (settings.prod) {
76
+ throw err // Fail fast in production
77
+ } else {
78
+ console.warn('⚠️ Moleculer initialization failed but continuing in development mode')
79
+ }
80
+ }
81
+
82
+ const broker = connector?.broker as MoleculerBroker
83
+
84
+ globalThis.moleculer = {
85
+ broker,
86
+
87
+ async call<T>(
88
+ actionName: string,
89
+ params?: any,
90
+ opts?: { timeout?: number; meta?: Record<string, any> },
91
+ ): Promise<T> {
92
+ const event = useEvent()
93
+ const tokenKey = event?.context?.tokenKey || getCookie(event, AccessTokenCookieKey)
94
+
95
+ if (!whitelistedActions.includes(actionName) && !tokenKey) {
96
+ throw BaseErrors.ServiceVoilation({
97
+ service: `Service ${actionName} communication`,
98
+ })
99
+ }
100
+
101
+ const user = whitelistedActions.includes(actionName)
102
+ ? NUXT_META.user
103
+ : tokenKey &&
104
+ (await connector!.call(
105
+ 'v1.Auth.resolveToken',
106
+ { tokenKey },
107
+ { meta: NUXT_META },
108
+ ))
109
+
110
+ const meta = { ...opts?.meta, tokenKey, user }
111
+ return connector!.call(actionName, params, {
112
+ timeout: opts?.timeout,
113
+ meta: meta,
114
+ })
115
+ },
116
+
117
+ async emit(eventName: string, payload?: any, groups?: string | string[]): Promise<void> {
118
+ await broker.emit(eventName, payload, groups)
119
+ },
120
+
121
+ async broadcast(
122
+ eventName: string,
123
+ payload?: any,
124
+ groups?: string | string[],
125
+ ): Promise<void> {
126
+ await broker.broadcast(eventName, payload, groups)
127
+ },
128
+ }
129
+
130
+ // Graceful shutdown
131
+ const shutdown = async () => {
132
+ console.log('🛑 Shutting down Moleculer broker...')
133
+ await connector?.stop()
134
+ console.log('✅ Moleculer broker stopped')
135
+ }
136
+
137
+ process.on('SIGINT', shutdown)
138
+ process.on('SIGTERM', shutdown)
139
+ })
@@ -0,0 +1,38 @@
1
+ // server/plugins/restate.ts
2
+ import { checkServiceHandler, clients, sendMessage } from '@empyria/restate'
3
+ import { getCookie } from 'h3'
4
+ import { AccessTokenCookieKey } from '../../app/consts'
5
+
6
+ import { settings } from '../env'
7
+
8
+ export default defineNitroPlugin(() => {
9
+ const restateURL = settings.restate.url
10
+ const restateAdminURL = settings.restate.adminURL
11
+
12
+ const restateClient = clients.connect({ url: restateURL })
13
+
14
+ globalThis.restate = {
15
+ client: restateClient,
16
+ adminURL: restateAdminURL,
17
+ discoverService: (serviceName: string, handlerName?: string) =>
18
+ checkServiceHandler(restateAdminURL, serviceName, handlerName),
19
+
20
+ async call<T>(serviceName: string, handlerName: string, params?: any): Promise<T> {
21
+ await checkServiceHandler(restateAdminURL, serviceName, handlerName)
22
+
23
+ const event = useEvent()
24
+ const tokenKey = event?.context?.tokenKey || getCookie(event, AccessTokenCookieKey)
25
+
26
+ const paramsWithToken = tokenKey ? { ...params, tokenKey } : params
27
+
28
+ return sendMessage({
29
+ restateURL,
30
+ name: serviceName,
31
+ message: handlerName,
32
+ payload: paramsWithToken,
33
+ })
34
+ },
35
+ }
36
+
37
+ console.log('✅ Restate client initialized')
38
+ })