@mxtommy/kip 3.10.0-beta.3 → 3.10.0-beta.31

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/plugin/index.js CHANGED
@@ -1,43 +1,214 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = (app) => {
36
+ const server_api_1 = require("@signalk/server-api");
37
+ const openapi = __importStar(require("./openApi.json"));
38
+ exports.default = (server) => {
39
+ const API_PATHS = {
40
+ DISPLAYS: `/displays`,
41
+ INSTANCE: `/displays/:displayId`,
42
+ ACTIVE_SCREEN: `/displays/:displayId/activeScreen`
43
+ };
44
+ // Helpers
45
+ function getDisplaySelfPath(displayId, suffix) {
46
+ const tail = suffix ? `.${suffix}` : '';
47
+ return server.getSelfPath(`displays.${displayId}${tail}`);
48
+ }
49
+ function readSelfDisplays() {
50
+ const fullPath = server.getSelfPath('displays'); // e.g., vessels.self.displays
51
+ return server.getPath(fullPath);
52
+ }
53
+ function sendOk(res, body) {
54
+ if (body === undefined)
55
+ return res.status(204).end();
56
+ return res.status(200).json(body);
57
+ }
58
+ function sendFail(res, statusCode, message) {
59
+ return res.status(statusCode).json({ state: 'FAILED', statusCode, message });
60
+ }
4
61
  const plugin = {
5
62
  id: 'kip',
6
63
  name: 'KIP',
7
64
  description: 'KIP server plugin',
8
65
  start: (settings) => {
9
- app.debug(`${plugin.name}: Starting plugin with settings: ${JSON.stringify(settings)}`);
10
- app.registerPutHandler('vessels.self', 'plugins.displays.*', displayPutHandler);
66
+ server.debug(`Starting plugin with settings: ${JSON.stringify(settings)}`);
67
+ server.setPluginStatus(`Starting...`);
11
68
  },
12
69
  stop: () => {
13
- app.debug(`${plugin.name}: Stopping plugin`);
70
+ server.debug(`Stopping plugin`);
71
+ const msg = 'Stopped.';
72
+ server.setPluginStatus(msg);
14
73
  },
15
74
  schema: () => {
16
- }
17
- };
18
- function displayPutHandler(context, path, value) {
19
- app.debug(`${plugin.name}: PUT handler called for context ${context}, path ${path}, value ${value}`);
20
- try {
21
- const delta = {
22
- updates: [
23
- {
24
- values: [
75
+ return {
76
+ type: "object",
77
+ properties: {}
78
+ };
79
+ },
80
+ registerWithRouter(router) {
81
+ server.debug(`Registering plugin routes: ${API_PATHS.DISPLAYS}, ${API_PATHS.INSTANCE}, ${API_PATHS.ACTIVE_SCREEN}`);
82
+ // Validate :displayId where present
83
+ router.param('displayId', (req, res, next, displayId) => {
84
+ if (!displayId || typeof displayId !== 'string') {
85
+ return sendFail(res, 400, 'Missing or invalid displayId parameter');
86
+ }
87
+ next();
88
+ });
89
+ router.put(`${API_PATHS.INSTANCE}`, async (req, res) => {
90
+ server.debug(`** PUT ${API_PATHS.INSTANCE}. Params: ${JSON.stringify(req.params)} Body: ${JSON.stringify(req.body)}`);
91
+ try {
92
+ const { displayId } = req.params;
93
+ const fullPath = getDisplaySelfPath(displayId);
94
+ server.debug(`Updating SK path ${fullPath} with body`);
95
+ server.handleMessage(plugin.id, {
96
+ updates: [
25
97
  {
26
- path: path,
27
- value: value
98
+ values: [
99
+ {
100
+ path: fullPath,
101
+ value: req.body ?? null
102
+ }
103
+ ]
28
104
  }
29
105
  ]
106
+ }, server_api_1.SKVersion.v1);
107
+ return res.status(200).json({ state: 'SUCCESS', statusCode: 200 });
108
+ }
109
+ catch (error) {
110
+ const msg = `HandleMessage failed with errors!`;
111
+ server.setPluginError(msg);
112
+ server.error(`Error in HandleMessage: ${error}`);
113
+ return sendFail(res, 400, error.message);
114
+ }
115
+ });
116
+ router.put(`${API_PATHS.ACTIVE_SCREEN}`, async (req, res) => {
117
+ server.debug(`** PUT ${API_PATHS.ACTIVE_SCREEN}. Params: ${JSON.stringify(req.params)} Body: ${JSON.stringify(req.body)}`);
118
+ try {
119
+ const { displayId } = req.params;
120
+ const fullPath = getDisplaySelfPath(displayId, 'activeScreen');
121
+ server.debug(`Updating SK path ${fullPath} with body.screenIdx`);
122
+ server.handleMessage(plugin.id, {
123
+ updates: [
124
+ {
125
+ values: [
126
+ {
127
+ path: fullPath,
128
+ value: req.body.screenIdx !== undefined ? req.body.screenIdx : null
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ }, server_api_1.SKVersion.v1);
134
+ return res.status(200).json({ state: 'SUCCESS', statusCode: 200 });
135
+ }
136
+ catch (error) {
137
+ const msg = `HandleMessage failed with errors!`;
138
+ server.setPluginError(msg);
139
+ server.error(`Error in HandleMessage: ${error}`);
140
+ return sendFail(res, 400, error.message);
141
+ }
142
+ });
143
+ router.get(API_PATHS.DISPLAYS, (req, res) => {
144
+ server.debug(`** GET ${API_PATHS.DISPLAYS}. Params: ${JSON.stringify(req.params)}`);
145
+ try {
146
+ const displays = readSelfDisplays();
147
+ const items = displays && typeof displays === 'object'
148
+ ? Object.entries(displays)
149
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
150
+ .filter(([_, v]) => v && typeof v === 'object')
151
+ .map(([displayId, v]) => ({
152
+ displayId,
153
+ displayName: v.displayName ?? null
154
+ }))
155
+ : [];
156
+ return res.status(200).json(items);
157
+ }
158
+ catch (error) {
159
+ server.error(`Error reading displays: ${String(error.message || error)}`);
160
+ return sendFail(res, 400, error.message);
161
+ }
162
+ });
163
+ router.get(`${API_PATHS.INSTANCE}`, (req, res) => {
164
+ server.debug(`** GET ${API_PATHS.INSTANCE}. Params: ${JSON.stringify(req.params)}`);
165
+ try {
166
+ const { displayId } = req.params;
167
+ if (!displayId) {
168
+ return sendFail(res, 400, 'Missing displayId parameter');
30
169
  }
31
- ]
32
- };
33
- app.debug(`${plugin.name}: Sending message: `, JSON.stringify(delta));
34
- app.handleMessage(plugin.id, delta);
35
- return { state: "COMPLETED", statusCode: 200 };
36
- }
37
- catch (error) {
38
- app.error(`${plugin.name}: Error in PUT handler: ${error}`);
39
- return { state: "COMPLETED", statusCode: 500, message: error.message };
40
- }
41
- }
170
+ const fullPath = getDisplaySelfPath(displayId);
171
+ const value = server.getPath(fullPath);
172
+ if (value === undefined) {
173
+ return sendFail(res, 404, `Display ${displayId} not found`);
174
+ }
175
+ return sendOk(res, value);
176
+ }
177
+ catch (error) {
178
+ server.error(`Error reading display ${req.params?.displayId}: ${String(error.message || error)}`);
179
+ return sendFail(res, 400, error.message);
180
+ }
181
+ });
182
+ router.get(`${API_PATHS.ACTIVE_SCREEN}`, (req, res) => {
183
+ server.debug(`** GET ${API_PATHS.ACTIVE_SCREEN}. Params: ${JSON.stringify(req.params)}`);
184
+ try {
185
+ const { displayId } = req.params;
186
+ if (!displayId) {
187
+ return sendFail(res, 400, 'Missing displayId parameter');
188
+ }
189
+ const fullPath = getDisplaySelfPath(displayId, 'activeScreen');
190
+ const value = server.getPath(fullPath);
191
+ if (value === undefined) {
192
+ return sendFail(res, 404, `Active screen for display ${displayId} not found`);
193
+ }
194
+ return sendOk(res, value);
195
+ }
196
+ catch (error) {
197
+ server.error(`Error reading activeScreen for ${req.params?.displayId}: ${String(error.message || error)}`);
198
+ return sendFail(res, 400, error.message);
199
+ }
200
+ });
201
+ // List all registered routes for debugging
202
+ if (router.stack) {
203
+ router.stack.forEach((layer) => {
204
+ if (layer.route && layer.route.path) {
205
+ server.debug(`Registered route: ${layer.route.stack[0].method.toUpperCase()} ${layer.route.path}`);
206
+ }
207
+ });
208
+ }
209
+ server.setPluginStatus(`Providing remote display screen control`);
210
+ },
211
+ getOpenApi: () => openapi
212
+ };
42
213
  return plugin;
43
214
  };
@@ -0,0 +1,141 @@
1
+ {
2
+ "openapi": "3.0.0",
3
+ "info": {
4
+ "version": "1.0.0",
5
+ "title": "KIP Remote Displays API",
6
+ "description": "API endpoints to list KIP displays and control the active screen for a given KIP instance via Signal K self.displays tree.\n\nUsage:\n- List displays:\n curl -s http://localhost:3000/plugins/kip/displays\n- Read a display entry:\n curl -s http://localhost:3000/plugins/kip/displays/{displayId}\n- Set a display entry:\n curl -s -X PUT -H 'Content-Type: application/json' -d '{\"displayName\":\"Mast\"}' http://localhost:3000/plugins/kip/displays/{displayId}\n- Get active screen:\n curl -s http://localhost:3000/plugins/kip/displays/{displayId}/activeScreen\n- Set active screen:\n curl -s -X PUT -H 'Content-Type: application/json' -d '{\"screenIdx\":1}' http://localhost:3000/plugins/kip/displays/{displayId}/activeScreen",
7
+ "termsOfService": "http://signalk.org/terms/",
8
+ "license": {
9
+ "name": "Apache 2.0",
10
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
11
+ }
12
+ },
13
+ "externalDocs": {
14
+ "url": "http://signalk.org/specification/",
15
+ "description": "Signal K specification."
16
+ },
17
+ "servers": [
18
+ { "url": "/" }
19
+ ],
20
+ "tags": [
21
+ { "name": "Displays", "description": "KIP display discovery and control." }
22
+ ],
23
+ "components": {
24
+ "schemas": {
25
+ "DisplayInfo": {
26
+ "type": "object",
27
+ "properties": {
28
+ "displayId": { "type": "string", "description": "KIP instance UUID" },
29
+ "displayName": { "type": "string", "nullable": true }
30
+ },
31
+ "required": ["displayId"]
32
+ },
33
+ "SuccessResponse": {
34
+ "type": "object",
35
+ "properties": {
36
+ "state": { "type": "string", "enum": ["SUCCESS"] },
37
+ "statusCode": { "type": "integer", "enum": [200] }
38
+ },
39
+ "required": ["state", "statusCode"]
40
+ },
41
+ "ErrorResponse": {
42
+ "type": "object",
43
+ "properties": {
44
+ "state": { "type": "string", "enum": ["FAILED"] },
45
+ "statusCode": { "type": "integer" },
46
+ "message": { "type": "string" }
47
+ },
48
+ "required": ["state", "statusCode", "message"]
49
+ },
50
+ "ActiveScreenSetRequest": {
51
+ "type": "object",
52
+ "properties": {
53
+ "screenIdx": { "type": "integer", "nullable": true, "description": "Index of active screen or null to clear" }
54
+ }
55
+ },
56
+ "AnyObjectOrNull": {
57
+ "oneOf": [
58
+ { "type": "object", "additionalProperties": true },
59
+ { "type": "null" }
60
+ ]
61
+ }
62
+ },
63
+ "parameters": {
64
+ "DisplayIdParam": {
65
+ "in": "path",
66
+ "required": true,
67
+ "name": "displayId",
68
+ "description": "KIP instance UUID",
69
+ "schema": { "type": "string" }
70
+ }
71
+ },
72
+ "securitySchemes": {
73
+ "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" },
74
+ "cookieAuth": { "type": "apiKey", "in": "cookie", "name": "JAUTHENTICATION" }
75
+ }
76
+ },
77
+ "security": [{ "cookieAuth": [] }, { "bearerAuth": [] }],
78
+ "paths": {
79
+ "/plugins/kip/displays": {
80
+ "get": {
81
+ "tags": ["Displays"],
82
+ "summary": "List available KIP displays",
83
+ "responses": {
84
+ "200": {
85
+ "description": "List of KIP instances discovered under self.displays",
86
+ "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/DisplayInfo" } } } }
87
+ },
88
+ "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
89
+ }
90
+ }
91
+ },
92
+ "/plugins/kip/displays/{displayId}": {
93
+ "parameters": [{ "$ref": "#/components/parameters/DisplayIdParam" }],
94
+ "get": {
95
+ "tags": ["Displays"],
96
+ "summary": "Get raw display entry under self.displays.{displayId}",
97
+ "responses": {
98
+ "200": { "description": "Display object", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnyObjectOrNull" } } } },
99
+ "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
100
+ "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
101
+ }
102
+ },
103
+ "put": {
104
+ "tags": ["Displays"],
105
+ "summary": "Set display entry under self.displays.{displayId}",
106
+ "requestBody": {
107
+ "required": false,
108
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnyObjectOrNull" } } }
109
+ },
110
+ "responses": {
111
+ "200": { "description": "Updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } },
112
+ "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
113
+ }
114
+ }
115
+ },
116
+ "/plugins/kip/displays/{displayId}/activeScreen": {
117
+ "parameters": [{ "$ref": "#/components/parameters/DisplayIdParam" }],
118
+ "get": {
119
+ "tags": ["Displays"],
120
+ "summary": "Get active screen index for display",
121
+ "responses": {
122
+ "200": { "description": "Active screen index or null", "content": { "application/json": { "schema": { "type": "integer", "nullable": true } } } },
123
+ "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
124
+ "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
125
+ }
126
+ },
127
+ "put": {
128
+ "tags": ["Displays"],
129
+ "summary": "Set active screen index for display",
130
+ "requestBody": {
131
+ "required": false,
132
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ActiveScreenSetRequest" } } }
133
+ },
134
+ "responses": {
135
+ "200": { "description": "Updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } },
136
+ "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
@@ -3,8 +3,8 @@ KIP supports multiple input modes for seamless navigation across devices.
3
3
 
4
4
  | Actions | Touch | Mouse | Keyboard Shortcuts |
5
5
  |----------------------------|--------------|------------------------------|----------------------------------------------------|
6
- | Open Actions menu | Swipe left | Click, drag left, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>←</kbd> (Left Arrow) |
7
- | Open Notification menu | Swipe right | Click, drag right, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>→</kbd> (Right Arrow) |
6
+ | Open Actions sidenav | Swipe left | Click, drag left, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>←</kbd> (Left Arrow) |
7
+ | Open Notification sidenav | Swipe right | Click, drag right, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>→</kbd> (Right Arrow) |
8
8
  | Cycle through dashboards | Swipe up/down | Click, drag up/down, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>↑</kbd>/<kbd>↓</kbd> (Up/Down Arrow) |
9
9
  | Toggle Fullscreen | N/A | N/A | <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>F</kbd> |
10
10
  | Toggle Night mode | N/A | N/A | <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>N</kbd> |