@bodhiapp/setup-modal-types 0.0.1
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/extension.ts +31 -0
- package/index.ts +84 -0
- package/lna.ts +75 -0
- package/message-types.ts +185 -0
- package/package.json +28 -0
- package/platform.ts +43 -0
- package/protocol.ts +97 -0
- package/server.ts +82 -0
- package/state.ts +65 -0
- package/type-guards.ts +126 -0
package/extension.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Extension error codes
|
|
2
|
+
export type ExtensionErrorCode = 'ext-not-installed' | 'ext-connection-failed' | 'ext-unsupported-version';
|
|
3
|
+
|
|
4
|
+
// Extension error code constants
|
|
5
|
+
export const EXT_NOT_INSTALLED: ExtensionErrorCode = 'ext-not-installed';
|
|
6
|
+
export const EXT_CONNECTION_FAILED: ExtensionErrorCode = 'ext-connection-failed';
|
|
7
|
+
export const EXT_UNSUPPORTED_VERSION: ExtensionErrorCode = 'ext-unsupported-version';
|
|
8
|
+
|
|
9
|
+
// Extension state interfaces
|
|
10
|
+
export interface ExtensionStateReady {
|
|
11
|
+
/** Current extension status */
|
|
12
|
+
status: 'ready';
|
|
13
|
+
/** Extension version */
|
|
14
|
+
version: string;
|
|
15
|
+
/** Extension ID (always present when ready) */
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ExtensionStateNotReady {
|
|
20
|
+
/** Current extension status */
|
|
21
|
+
status: 'unreachable' | 'not-installed' | 'unsupported';
|
|
22
|
+
/** Error details */
|
|
23
|
+
error: {
|
|
24
|
+
/** Error message */
|
|
25
|
+
message: string;
|
|
26
|
+
/** Error code */
|
|
27
|
+
code: ExtensionErrorCode;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ExtensionState = ExtensionStateReady | ExtensionStateNotReady;
|
package/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consolidated types for setup-modal
|
|
3
|
+
*
|
|
4
|
+
* This folder contains all domain and protocol types organized by domain.
|
|
5
|
+
* It is designed to be independent and copyable to other packages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Platform types
|
|
9
|
+
export type { BrowserType, OSType, EnvState, SupportedBrowser, NotSupportedBrowser, Browser, SupportedOS, NotSupportedOS, OS } from './platform';
|
|
10
|
+
|
|
11
|
+
// Extension types
|
|
12
|
+
export type { ExtensionErrorCode, ExtensionStateReady, ExtensionStateNotReady, ExtensionState } from './extension';
|
|
13
|
+
export { EXT_NOT_INSTALLED, EXT_CONNECTION_FAILED, EXT_UNSUPPORTED_VERSION } from './extension';
|
|
14
|
+
|
|
15
|
+
// Server types
|
|
16
|
+
export type { ServerErrorCode, ServerStateReady, ServerStateReachable, ServerStatePending, ServerStateUnreachable, ServerStateError, ServerState } from './server';
|
|
17
|
+
export {
|
|
18
|
+
SERVER_PENDING_EXT_READY,
|
|
19
|
+
SERVER_CONN_REFUSED,
|
|
20
|
+
SERVER_CONN_TIMEOUT,
|
|
21
|
+
SERVER_NOT_FOUND,
|
|
22
|
+
SERVER_NETWORK_UNREACHABLE,
|
|
23
|
+
SERVER_SERVICE_UNAVAILABLE,
|
|
24
|
+
SERVER_UNEXPECTED_ERROR,
|
|
25
|
+
SERVER_IN_SETUP_STATUS,
|
|
26
|
+
SERVER_IN_ADMIN_STATUS,
|
|
27
|
+
} from './server';
|
|
28
|
+
|
|
29
|
+
// LNA types
|
|
30
|
+
export type {
|
|
31
|
+
LnaErrorCode,
|
|
32
|
+
LnaStatePrompt,
|
|
33
|
+
LnaStateSkipped,
|
|
34
|
+
LnaStateGranted,
|
|
35
|
+
LnaStateUnreachable,
|
|
36
|
+
LnaStateDenied,
|
|
37
|
+
LnaStateUnsupported,
|
|
38
|
+
LnaState,
|
|
39
|
+
LnaServerStatePending,
|
|
40
|
+
LnaServerStateReady,
|
|
41
|
+
LnaServerStateSetup,
|
|
42
|
+
LnaServerStateResourceAdmin,
|
|
43
|
+
LnaServerStateError,
|
|
44
|
+
LnaServerState,
|
|
45
|
+
} from './lna';
|
|
46
|
+
export { LNA_UNREACHABLE, LNA_PERMISSION_DENIED } from './lna';
|
|
47
|
+
|
|
48
|
+
// State types
|
|
49
|
+
export { SetupStep, DEFAULT_USER_CONFIRMATIONS, DEFAULT_SETUP_STATE } from './state';
|
|
50
|
+
export type { SelectedConnection, UserConfirmations, SetupState } from './state';
|
|
51
|
+
|
|
52
|
+
// Protocol types
|
|
53
|
+
export type { RequestId, MessageKind, RequestMessage, ResponseMessage, ErrorMessage, EventMessage, ProtocolMessage } from './protocol';
|
|
54
|
+
export { isRequestMessage, isResponseMessage, isErrorMessage, isEventMessage } from './protocol';
|
|
55
|
+
|
|
56
|
+
// Message types
|
|
57
|
+
export type { MessageTypeRegistry, MessageType, RequestPayload, ResponsePayload, RequestHandlers } from './message-types';
|
|
58
|
+
export { MSG, isMessageType } from './message-types';
|
|
59
|
+
|
|
60
|
+
// Type guards
|
|
61
|
+
export {
|
|
62
|
+
isExtensionStateReady,
|
|
63
|
+
isExtensionStateNotReady,
|
|
64
|
+
isServerStateReady,
|
|
65
|
+
isServerStateReachable,
|
|
66
|
+
isServerStatePending,
|
|
67
|
+
isServerStateUnreachable,
|
|
68
|
+
isServerStateError,
|
|
69
|
+
isLnaStatePrompt,
|
|
70
|
+
isLnaStateSkipped,
|
|
71
|
+
isLnaStateGranted,
|
|
72
|
+
isLnaStateUnreachable,
|
|
73
|
+
isLnaStateDenied,
|
|
74
|
+
isLnaStateUnsupported,
|
|
75
|
+
isLnaServerStatePending,
|
|
76
|
+
isLnaServerStateReady,
|
|
77
|
+
isLnaServerStateSetup,
|
|
78
|
+
isLnaServerStateResourceAdmin,
|
|
79
|
+
isLnaServerStateError,
|
|
80
|
+
isSupportedBrowser,
|
|
81
|
+
isNotSupportedBrowser,
|
|
82
|
+
isSupportedOS,
|
|
83
|
+
isNotSupportedOS,
|
|
84
|
+
} from './type-guards';
|
package/lna.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// LNA error codes
|
|
2
|
+
export type LnaErrorCode = 'lna-unreachable' | 'lna-permission-denied';
|
|
3
|
+
|
|
4
|
+
// LNA error code constants
|
|
5
|
+
export const LNA_UNREACHABLE: LnaErrorCode = 'lna-unreachable';
|
|
6
|
+
export const LNA_PERMISSION_DENIED: LnaErrorCode = 'lna-permission-denied';
|
|
7
|
+
|
|
8
|
+
// LNA (Local Network Access) state interfaces
|
|
9
|
+
// Aligned with browser permission API states: prompt, granted, denied
|
|
10
|
+
export interface LnaStatePrompt {
|
|
11
|
+
status: 'prompt';
|
|
12
|
+
serverUrl?: string; // For URL input default from localStorage
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LnaStateSkipped {
|
|
16
|
+
status: 'skipped';
|
|
17
|
+
serverUrl?: string; // For URL input default from localStorage
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LnaStateGranted {
|
|
21
|
+
status: 'granted';
|
|
22
|
+
serverUrl: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LnaStateUnreachable {
|
|
26
|
+
status: 'unreachable';
|
|
27
|
+
serverUrl: string;
|
|
28
|
+
error: {
|
|
29
|
+
message: string;
|
|
30
|
+
code: LnaErrorCode;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LnaStateDenied {
|
|
35
|
+
status: 'denied';
|
|
36
|
+
error: {
|
|
37
|
+
message: string;
|
|
38
|
+
code: LnaErrorCode;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface LnaStateUnsupported {
|
|
43
|
+
status: 'unsupported';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type LnaState = LnaStatePrompt | LnaStateSkipped | LnaStateGranted | LnaStateUnreachable | LnaStateDenied | LnaStateUnsupported;
|
|
47
|
+
|
|
48
|
+
// LNA Server state interfaces
|
|
49
|
+
export interface LnaServerStatePending {
|
|
50
|
+
status: 'pending-lna-ready';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LnaServerStateReady {
|
|
54
|
+
status: 'ready';
|
|
55
|
+
version: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface LnaServerStateSetup {
|
|
59
|
+
status: 'setup';
|
|
60
|
+
version: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface LnaServerStateResourceAdmin {
|
|
64
|
+
status: 'resource-admin';
|
|
65
|
+
version: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface LnaServerStateError {
|
|
69
|
+
status: 'error';
|
|
70
|
+
error: {
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type LnaServerState = LnaServerStatePending | LnaServerStateReady | LnaServerStateSetup | LnaServerStateResourceAdmin | LnaServerStateError;
|
package/message-types.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Type Registry - Single source of truth for all protocol message types
|
|
3
|
+
*
|
|
4
|
+
* This registry maps message type strings to their payload/response shapes.
|
|
5
|
+
* All other types (RequestPayload, ResponsePayload, etc.) are derived from this registry.
|
|
6
|
+
*
|
|
7
|
+
* Design: Each entry defines:
|
|
8
|
+
* - request: The payload type sent with the request
|
|
9
|
+
* - response: The payload type expected in the response
|
|
10
|
+
*
|
|
11
|
+
* For events (one-way messages), response is void.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SetupState } from './state';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Central registry mapping message types to their payload/response shapes
|
|
18
|
+
* Single source of truth - everything else is inferred!
|
|
19
|
+
*/
|
|
20
|
+
export interface MessageTypeRegistry {
|
|
21
|
+
// === Modal → Parent (Commands) ===
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Modal is ready and requesting initial state
|
|
25
|
+
* Sent on modal mount
|
|
26
|
+
*/
|
|
27
|
+
'modal:ready': {
|
|
28
|
+
request: void;
|
|
29
|
+
response: { setupState: SetupState };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* User requested to refresh platform detection
|
|
34
|
+
* Triggers re-detection of browser, OS, extension, server
|
|
35
|
+
*/
|
|
36
|
+
'modal:refresh': {
|
|
37
|
+
request: void;
|
|
38
|
+
response: { setupState: SetupState };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* User requested to close the modal
|
|
43
|
+
* Parent should hide/destroy modal
|
|
44
|
+
*/
|
|
45
|
+
'modal:close': {
|
|
46
|
+
request: void;
|
|
47
|
+
response: void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Setup completed successfully
|
|
52
|
+
* Signals parent that user has finished setup
|
|
53
|
+
*/
|
|
54
|
+
'modal:complete': {
|
|
55
|
+
request: void;
|
|
56
|
+
response: void;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* User requested LNA connection to specific server URL
|
|
61
|
+
* Triggers LNA permission request and connection attempt
|
|
62
|
+
*/
|
|
63
|
+
'modal:lna:connect': {
|
|
64
|
+
request: { serverUrl: string };
|
|
65
|
+
response: { success: boolean };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* User chose to skip LNA setup
|
|
70
|
+
* Falls back to extension-only mode
|
|
71
|
+
*/
|
|
72
|
+
'modal:lna:skip': {
|
|
73
|
+
request: void;
|
|
74
|
+
response: { success: boolean };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* User confirmed server installation status
|
|
79
|
+
* Used when server needs to be installed
|
|
80
|
+
*/
|
|
81
|
+
'modal:confirm-server-install': {
|
|
82
|
+
request: { confirmed: boolean };
|
|
83
|
+
response: { success: boolean };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* User selected preferred connection method
|
|
88
|
+
* Allows choosing between LNA and Extension paths
|
|
89
|
+
*/
|
|
90
|
+
'modal:select-connection': {
|
|
91
|
+
request: { connection: 'lna' | 'extension' };
|
|
92
|
+
response: { success: boolean };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// === Parent → Modal (Events) ===
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Parent sending updated state to modal
|
|
99
|
+
* Fired when platform detection results change
|
|
100
|
+
*/
|
|
101
|
+
'parent:state-update': {
|
|
102
|
+
request: { setupState: SetupState };
|
|
103
|
+
response: void;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// === Derived Types with Full Inference ===
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Union of all valid message type strings
|
|
111
|
+
* Derived from MessageTypeRegistry keys
|
|
112
|
+
*/
|
|
113
|
+
export type MessageType = keyof MessageTypeRegistry;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Extract request payload type for a given message type
|
|
117
|
+
* Returns the 'request' field from the registry entry
|
|
118
|
+
*/
|
|
119
|
+
export type RequestPayload<T extends MessageType> = MessageTypeRegistry[T]['request'];
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extract response payload type for a given message type
|
|
123
|
+
* Returns the 'response' field from the registry entry
|
|
124
|
+
*/
|
|
125
|
+
export type ResponsePayload<T extends MessageType> = MessageTypeRegistry[T]['response'];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Message type constants - Type-safe identifiers
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* switch (message.type) {
|
|
132
|
+
* case MSG.MODAL_READY:
|
|
133
|
+
* // Type-safe constant, no typos possible
|
|
134
|
+
* break;
|
|
135
|
+
* }
|
|
136
|
+
*/
|
|
137
|
+
export const MSG = {
|
|
138
|
+
// Modal lifecycle
|
|
139
|
+
MODAL_READY: 'modal:ready',
|
|
140
|
+
MODAL_REFRESH: 'modal:refresh',
|
|
141
|
+
MODAL_CLOSE: 'modal:close',
|
|
142
|
+
MODAL_COMPLETE: 'modal:complete',
|
|
143
|
+
|
|
144
|
+
// LNA actions
|
|
145
|
+
MODAL_LNA_CONNECT: 'modal:lna:connect',
|
|
146
|
+
MODAL_LNA_SKIP: 'modal:lna:skip',
|
|
147
|
+
|
|
148
|
+
// Server confirmation
|
|
149
|
+
MODAL_CONFIRM_SERVER_INSTALL: 'modal:confirm-server-install',
|
|
150
|
+
|
|
151
|
+
// Connection selection
|
|
152
|
+
MODAL_SELECT_CONNECTION: 'modal:select-connection',
|
|
153
|
+
|
|
154
|
+
// Parent → Modal events
|
|
155
|
+
PARENT_STATE_UPDATE: 'parent:state-update',
|
|
156
|
+
} as const;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Type guard that narrows RequestMessage to specific type WITH payload typing
|
|
160
|
+
*
|
|
161
|
+
* Use this for full type safety when you need access to typed payload fields.
|
|
162
|
+
* The guard narrows the generic RequestMessage to RequestMessage<T> where T
|
|
163
|
+
* determines the payload type.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* if (isMessageType(msg, MSG.MODAL_LNA_CONNECT)) {
|
|
167
|
+
* // msg.payload is { serverUrl: string } - fully typed!
|
|
168
|
+
* console.log(msg.payload.serverUrl); // Autocomplete works!
|
|
169
|
+
* }
|
|
170
|
+
*/
|
|
171
|
+
export function isMessageType<T extends MessageType>(msg: { type: string }, type: T): msg is { type: T } {
|
|
172
|
+
return msg.type === type;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Type-safe request handler map
|
|
177
|
+
* Enforces correct payload access AND correct return type for each message
|
|
178
|
+
*
|
|
179
|
+
* Each handler receives a RequestMessage<K> where K is the specific message type,
|
|
180
|
+
* providing full type safety for payload access. The handler must return the
|
|
181
|
+
* correct ResponsePayload<K> type.
|
|
182
|
+
*/
|
|
183
|
+
export type RequestHandlers = {
|
|
184
|
+
[K in MessageType]?: (msg: { type: K; requestId: string; payload: RequestPayload<K> }) => ResponsePayload<K>;
|
|
185
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bodhiapp/setup-modal-types",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "TypeScript types for Bodhi Setup Modal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"types": "index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.ts",
|
|
11
|
+
"import": "./index.ts",
|
|
12
|
+
"require": "./index.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"*.ts"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/BodhiSearch/bodhi-browser.git",
|
|
21
|
+
"directory": "setup-modal/src/types"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "BodhiSearch",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/platform.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Browser and OS type definitions
|
|
2
|
+
export type BrowserType = 'chrome' | 'edge' | 'firefox' | 'safari' | 'unknown';
|
|
3
|
+
export type OSType = 'macos' | 'windows' | 'linux' | 'unknown';
|
|
4
|
+
|
|
5
|
+
// Environment state interface
|
|
6
|
+
export interface EnvState {
|
|
7
|
+
os: OSType;
|
|
8
|
+
browser: BrowserType;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Browser platform definitions
|
|
12
|
+
export interface SupportedBrowser {
|
|
13
|
+
id: BrowserType;
|
|
14
|
+
status: 'supported';
|
|
15
|
+
name: string;
|
|
16
|
+
extension_url: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface NotSupportedBrowser {
|
|
20
|
+
id: BrowserType;
|
|
21
|
+
status: 'not-supported';
|
|
22
|
+
name: string;
|
|
23
|
+
github_issue_url?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type Browser = SupportedBrowser | NotSupportedBrowser;
|
|
27
|
+
|
|
28
|
+
// OS platform definitions
|
|
29
|
+
export interface SupportedOS {
|
|
30
|
+
id: OSType;
|
|
31
|
+
status: 'supported';
|
|
32
|
+
name: string;
|
|
33
|
+
download_url: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NotSupportedOS {
|
|
37
|
+
id: OSType;
|
|
38
|
+
status: 'not-supported';
|
|
39
|
+
name: string;
|
|
40
|
+
github_issue_url?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type OS = SupportedOS | NotSupportedOS;
|
package/protocol.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core protocol types for setup-modal ↔ parent communication
|
|
3
|
+
*
|
|
4
|
+
* Design principles:
|
|
5
|
+
* - Type-safe request/response correlation
|
|
6
|
+
* - Discriminated unions for message kinds
|
|
7
|
+
* - Branded types for ID safety
|
|
8
|
+
* - Full TypeScript inference from MessageTypeRegistry
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { MessageType, RequestPayload, ResponsePayload } from './message-types';
|
|
12
|
+
|
|
13
|
+
/** Branded type for type-safe request IDs */
|
|
14
|
+
export type RequestId = string & { readonly __brand: 'RequestId' };
|
|
15
|
+
|
|
16
|
+
/** Message kind discriminator */
|
|
17
|
+
export type MessageKind = 'request' | 'response' | 'error' | 'event';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Request message - expects a response
|
|
21
|
+
* Sent by either modal or parent to request an action
|
|
22
|
+
*/
|
|
23
|
+
export interface RequestMessage<T extends MessageType = MessageType> {
|
|
24
|
+
readonly kind: 'request';
|
|
25
|
+
readonly type: T;
|
|
26
|
+
readonly requestId: RequestId;
|
|
27
|
+
readonly payload: RequestPayload<T>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Response message - correlates to a request
|
|
32
|
+
* Sent in response to a RequestMessage with matching requestId
|
|
33
|
+
*/
|
|
34
|
+
export interface ResponseMessage<T extends MessageType = MessageType> {
|
|
35
|
+
readonly kind: 'response';
|
|
36
|
+
readonly type: T;
|
|
37
|
+
readonly requestId: RequestId;
|
|
38
|
+
readonly payload: ResponsePayload<T>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Error response - indicates request failure
|
|
43
|
+
* Sent instead of ResponseMessage when request cannot be fulfilled
|
|
44
|
+
*/
|
|
45
|
+
export interface ErrorMessage {
|
|
46
|
+
readonly kind: 'error';
|
|
47
|
+
readonly requestId: RequestId;
|
|
48
|
+
readonly error: {
|
|
49
|
+
code: string;
|
|
50
|
+
message: string;
|
|
51
|
+
details?: unknown;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Event message - fire-and-forget notification
|
|
57
|
+
* No response expected, used for one-way state updates
|
|
58
|
+
*/
|
|
59
|
+
export interface EventMessage<T extends MessageType = MessageType> {
|
|
60
|
+
readonly kind: 'event';
|
|
61
|
+
readonly type: T;
|
|
62
|
+
readonly payload: RequestPayload<T>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Union of all protocol messages
|
|
67
|
+
* Discriminated by 'kind' field for type narrowing
|
|
68
|
+
*/
|
|
69
|
+
export type ProtocolMessage = RequestMessage | ResponseMessage | ErrorMessage | EventMessage;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if message is a request
|
|
73
|
+
*/
|
|
74
|
+
export function isRequestMessage(msg: ProtocolMessage): msg is RequestMessage {
|
|
75
|
+
return msg.kind === 'request';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Type guard to check if message is a response
|
|
80
|
+
*/
|
|
81
|
+
export function isResponseMessage(msg: ProtocolMessage): msg is ResponseMessage {
|
|
82
|
+
return msg.kind === 'response';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Type guard to check if message is an error
|
|
87
|
+
*/
|
|
88
|
+
export function isErrorMessage(msg: ProtocolMessage): msg is ErrorMessage {
|
|
89
|
+
return msg.kind === 'error';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Type guard to check if message is an event
|
|
94
|
+
*/
|
|
95
|
+
export function isEventMessage(msg: ProtocolMessage): msg is EventMessage {
|
|
96
|
+
return msg.kind === 'event';
|
|
97
|
+
}
|
package/server.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Server error codes
|
|
2
|
+
export type ServerErrorCode =
|
|
3
|
+
| 'server-pending-ext-ready'
|
|
4
|
+
| 'server-conn-refused'
|
|
5
|
+
| 'server-conn-timeout'
|
|
6
|
+
| 'server-not-found'
|
|
7
|
+
| 'server-network-unreachable'
|
|
8
|
+
| 'server-service-unavailable'
|
|
9
|
+
| 'server-unexpected-error'
|
|
10
|
+
| 'server-in-setup-status'
|
|
11
|
+
| 'server-in-admin-status';
|
|
12
|
+
|
|
13
|
+
// Server error code constants
|
|
14
|
+
export const SERVER_PENDING_EXT_READY: ServerErrorCode = 'server-pending-ext-ready';
|
|
15
|
+
export const SERVER_CONN_REFUSED: ServerErrorCode = 'server-conn-refused';
|
|
16
|
+
export const SERVER_CONN_TIMEOUT: ServerErrorCode = 'server-conn-timeout';
|
|
17
|
+
export const SERVER_NOT_FOUND: ServerErrorCode = 'server-not-found';
|
|
18
|
+
export const SERVER_NETWORK_UNREACHABLE: ServerErrorCode = 'server-network-unreachable';
|
|
19
|
+
export const SERVER_SERVICE_UNAVAILABLE: ServerErrorCode = 'server-service-unavailable';
|
|
20
|
+
export const SERVER_UNEXPECTED_ERROR: ServerErrorCode = 'server-unexpected-error';
|
|
21
|
+
export const SERVER_IN_SETUP_STATUS: ServerErrorCode = 'server-in-setup-status';
|
|
22
|
+
export const SERVER_IN_ADMIN_STATUS: ServerErrorCode = 'server-in-admin-status';
|
|
23
|
+
|
|
24
|
+
// Server state interfaces
|
|
25
|
+
export interface ServerStateReady {
|
|
26
|
+
/** Current server status */
|
|
27
|
+
status: 'ready';
|
|
28
|
+
/** Server version */
|
|
29
|
+
version: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ServerStateReachable {
|
|
33
|
+
/** Current server status */
|
|
34
|
+
status: 'setup' | 'resource-admin';
|
|
35
|
+
/** Server version */
|
|
36
|
+
version: string;
|
|
37
|
+
/** Error details */
|
|
38
|
+
error: {
|
|
39
|
+
/** Error message */
|
|
40
|
+
message: string;
|
|
41
|
+
/** Error code */
|
|
42
|
+
code: ServerErrorCode;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ServerStatePending {
|
|
47
|
+
/** Current server status */
|
|
48
|
+
status: 'pending-extension-ready';
|
|
49
|
+
/** Error details */
|
|
50
|
+
error: {
|
|
51
|
+
/** Error message */
|
|
52
|
+
message: string;
|
|
53
|
+
/** Error code */
|
|
54
|
+
code: ServerErrorCode;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ServerStateUnreachable {
|
|
59
|
+
/** Current server status */
|
|
60
|
+
status: 'unreachable';
|
|
61
|
+
/** Error details */
|
|
62
|
+
error: {
|
|
63
|
+
/** Error message */
|
|
64
|
+
message: string;
|
|
65
|
+
/** Error code */
|
|
66
|
+
code: ServerErrorCode;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ServerStateError {
|
|
71
|
+
/** Current server status */
|
|
72
|
+
status: 'error';
|
|
73
|
+
/** Error details */
|
|
74
|
+
error: {
|
|
75
|
+
/** Error message */
|
|
76
|
+
message: string;
|
|
77
|
+
/** Error code */
|
|
78
|
+
code: ServerErrorCode;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type ServerState = ServerStateReady | ServerStateReachable | ServerStatePending | ServerStateUnreachable | ServerStateError;
|
package/state.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Browser, EnvState, OS } from './platform';
|
|
2
|
+
import type { ExtensionState } from './extension';
|
|
3
|
+
import type { ServerState } from './server';
|
|
4
|
+
import type { LnaServerState, LnaState } from './lna';
|
|
5
|
+
|
|
6
|
+
// Step enum for navigation
|
|
7
|
+
export enum SetupStep {
|
|
8
|
+
PLATFORM_CHECK = 'platform-check',
|
|
9
|
+
SERVER_SETUP = 'server-setup',
|
|
10
|
+
LNA_SETUP = 'lna-setup',
|
|
11
|
+
EXTENSION_SETUP = 'extension-setup',
|
|
12
|
+
COMPLETE = 'complete',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Selected connection type for user preference
|
|
16
|
+
export type SelectedConnection = 'lna' | 'extension' | null;
|
|
17
|
+
|
|
18
|
+
// User confirmations interface for manual confirmation states
|
|
19
|
+
export interface UserConfirmations {
|
|
20
|
+
/** Whether user has confirmed server installation */
|
|
21
|
+
serverInstall: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Default user confirmations
|
|
25
|
+
export const DEFAULT_USER_CONFIRMATIONS: UserConfirmations = {
|
|
26
|
+
serverInstall: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Main setup state interface
|
|
30
|
+
export interface SetupState {
|
|
31
|
+
/** Extension state details */
|
|
32
|
+
extension: ExtensionState;
|
|
33
|
+
/** Server state details (via extension) */
|
|
34
|
+
server: ServerState;
|
|
35
|
+
/** LNA connection state */
|
|
36
|
+
lna: LnaState;
|
|
37
|
+
/** Server state details (via LNA) */
|
|
38
|
+
lnaServer: LnaServerState;
|
|
39
|
+
/** Environment detection */
|
|
40
|
+
env: EnvState;
|
|
41
|
+
/** Browser platforms list */
|
|
42
|
+
browsers: Browser[];
|
|
43
|
+
/** Operating systems list */
|
|
44
|
+
os: OS[];
|
|
45
|
+
/** User confirmations for manual steps */
|
|
46
|
+
userConfirmations: UserConfirmations;
|
|
47
|
+
/** User's preferred connection method (null = auto-select based on priority) */
|
|
48
|
+
selectedConnection: SelectedConnection;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default setup state used during initialization before parent sends real state
|
|
53
|
+
* Represents "loading" state - unknown platform, no extension, no server
|
|
54
|
+
*/
|
|
55
|
+
export const DEFAULT_SETUP_STATE: SetupState = {
|
|
56
|
+
extension: { status: 'not-installed', error: { message: 'Loading...', code: 'ext-not-installed' } },
|
|
57
|
+
server: { status: 'pending-extension-ready', error: { message: 'Loading...', code: 'server-pending-ext-ready' } },
|
|
58
|
+
lna: { status: 'prompt' },
|
|
59
|
+
lnaServer: { status: 'pending-lna-ready' },
|
|
60
|
+
env: { browser: 'unknown', os: 'unknown' },
|
|
61
|
+
browsers: [],
|
|
62
|
+
os: [],
|
|
63
|
+
userConfirmations: DEFAULT_USER_CONFIRMATIONS,
|
|
64
|
+
selectedConnection: null,
|
|
65
|
+
};
|
package/type-guards.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { ExtensionState, ExtensionStateNotReady, ExtensionStateReady } from './extension';
|
|
2
|
+
import type { ServerState, ServerStateError, ServerStatePending, ServerStateReachable, ServerStateReady, ServerStateUnreachable } from './server';
|
|
3
|
+
import type {
|
|
4
|
+
LnaServerState,
|
|
5
|
+
LnaServerStateError,
|
|
6
|
+
LnaServerStatePending,
|
|
7
|
+
LnaServerStateReady,
|
|
8
|
+
LnaServerStateResourceAdmin,
|
|
9
|
+
LnaServerStateSetup,
|
|
10
|
+
LnaState,
|
|
11
|
+
LnaStateDenied,
|
|
12
|
+
LnaStateGranted,
|
|
13
|
+
LnaStatePrompt,
|
|
14
|
+
LnaStateSkipped,
|
|
15
|
+
LnaStateUnreachable,
|
|
16
|
+
LnaStateUnsupported,
|
|
17
|
+
} from './lna';
|
|
18
|
+
import type { Browser, NotSupportedBrowser, NotSupportedOS, OS, SupportedBrowser, SupportedOS } from './platform';
|
|
19
|
+
|
|
20
|
+
// ============================================
|
|
21
|
+
// Extension Type Guards
|
|
22
|
+
// ============================================
|
|
23
|
+
|
|
24
|
+
export function isExtensionStateReady(ext: ExtensionState): ext is ExtensionStateReady {
|
|
25
|
+
return ext.status === 'ready';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isExtensionStateNotReady(ext: ExtensionState): ext is ExtensionStateNotReady {
|
|
29
|
+
return ext.status !== 'ready';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================
|
|
33
|
+
// Server Type Guards
|
|
34
|
+
// ============================================
|
|
35
|
+
|
|
36
|
+
export function isServerStateReady(server: ServerState): server is ServerStateReady {
|
|
37
|
+
return server.status === 'ready';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isServerStateReachable(server: ServerState): server is ServerStateReachable {
|
|
41
|
+
return server.status === 'setup' || server.status === 'resource-admin';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isServerStatePending(server: ServerState): server is ServerStatePending {
|
|
45
|
+
return server.status === 'pending-extension-ready';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isServerStateUnreachable(server: ServerState): server is ServerStateUnreachable {
|
|
49
|
+
return server.status === 'unreachable';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isServerStateError(server: ServerState): server is ServerStateError {
|
|
53
|
+
return server.status === 'error';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// LNA Type Guards
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
export function isLnaStatePrompt(lna: LnaState): lna is LnaStatePrompt {
|
|
61
|
+
return lna.status === 'prompt';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isLnaStateSkipped(lna: LnaState): lna is LnaStateSkipped {
|
|
65
|
+
return lna.status === 'skipped';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isLnaStateGranted(lna: LnaState): lna is LnaStateGranted {
|
|
69
|
+
return lna.status === 'granted';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isLnaStateUnreachable(lna: LnaState): lna is LnaStateUnreachable {
|
|
73
|
+
return lna.status === 'unreachable';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isLnaStateDenied(lna: LnaState): lna is LnaStateDenied {
|
|
77
|
+
return lna.status === 'denied';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isLnaStateUnsupported(lna: LnaState): lna is LnaStateUnsupported {
|
|
81
|
+
return lna.status === 'unsupported';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================
|
|
85
|
+
// LNA Server Type Guards
|
|
86
|
+
// ============================================
|
|
87
|
+
|
|
88
|
+
export function isLnaServerStatePending(server: LnaServerState): server is LnaServerStatePending {
|
|
89
|
+
return server.status === 'pending-lna-ready';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isLnaServerStateReady(server: LnaServerState): server is LnaServerStateReady {
|
|
93
|
+
return server.status === 'ready';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isLnaServerStateSetup(server: LnaServerState): server is LnaServerStateSetup {
|
|
97
|
+
return server.status === 'setup';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isLnaServerStateResourceAdmin(server: LnaServerState): server is LnaServerStateResourceAdmin {
|
|
101
|
+
return server.status === 'resource-admin';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function isLnaServerStateError(server: LnaServerState): server is LnaServerStateError {
|
|
105
|
+
return server.status === 'error';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================
|
|
109
|
+
// Browser/OS Type Guards
|
|
110
|
+
// ============================================
|
|
111
|
+
|
|
112
|
+
export function isSupportedBrowser(browser: Browser): browser is SupportedBrowser {
|
|
113
|
+
return browser.status === 'supported';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function isNotSupportedBrowser(browser: Browser): browser is NotSupportedBrowser {
|
|
117
|
+
return browser.status === 'not-supported';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function isSupportedOS(os: OS): os is SupportedOS {
|
|
121
|
+
return os.status === 'supported';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function isNotSupportedOS(os: OS): os is NotSupportedOS {
|
|
125
|
+
return os.status === 'not-supported';
|
|
126
|
+
}
|