@relazio/plugin-sdk 0.1.0

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.
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ExpressServer = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const https_1 = __importDefault(require("https"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ /**
11
+ * Server Express per il plugin
12
+ */
13
+ class ExpressServer {
14
+ constructor(plugin, options) {
15
+ this.plugin = plugin;
16
+ this.options = options;
17
+ this.app = (0, express_1.default)();
18
+ this.setupMiddleware();
19
+ this.setupRoutes();
20
+ }
21
+ /**
22
+ * Setup middleware
23
+ */
24
+ setupMiddleware() {
25
+ // Body parser
26
+ this.app.use(express_1.default.json({ limit: '10mb' }));
27
+ // CORS
28
+ this.app.use((req, res, next) => {
29
+ res.header('Access-Control-Allow-Origin', '*');
30
+ res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
31
+ res.header('Access-Control-Allow-Headers', 'Content-Type, X-Organization-Id');
32
+ if (req.method === 'OPTIONS') {
33
+ res.sendStatus(200);
34
+ }
35
+ else {
36
+ next();
37
+ }
38
+ });
39
+ // Request logging
40
+ this.app.use((req, res, next) => {
41
+ console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
42
+ next();
43
+ });
44
+ // Error handling
45
+ this.app.use((err, req, res, next) => {
46
+ console.error('Error:', err);
47
+ res.status(500).json({
48
+ error: 'Internal server error',
49
+ message: err.message,
50
+ });
51
+ });
52
+ }
53
+ /**
54
+ * Setup routes
55
+ */
56
+ setupRoutes() {
57
+ // Health check
58
+ this.app.get('/health', (req, res) => {
59
+ const config = this.plugin.getConfig();
60
+ res.json({
61
+ status: 'ok',
62
+ plugin: config.id,
63
+ version: config.version,
64
+ uptime: process.uptime(),
65
+ transforms: {
66
+ sync: this.plugin.getTransforms().length,
67
+ async: this.plugin.getAsyncTransforms().length,
68
+ },
69
+ });
70
+ });
71
+ // Registration endpoint (multi-tenant)
72
+ if (this.options.multiTenant) {
73
+ this.app.post('/register', async (req, res) => {
74
+ try {
75
+ const registry = this.plugin.getRegistry();
76
+ if (!registry) {
77
+ res.status(501).json({ error: 'Multi-tenant not enabled' });
78
+ return;
79
+ }
80
+ const result = await registry.register(req.body);
81
+ res.json(result);
82
+ }
83
+ catch (error) {
84
+ const message = error instanceof Error ? error.message : 'Unknown error';
85
+ console.error('Registration error:', error);
86
+ res.status(500).json({ error: 'Registration failed', message });
87
+ }
88
+ });
89
+ // Unregister endpoint
90
+ this.app.post('/unregister', async (req, res) => {
91
+ try {
92
+ const registry = this.plugin.getRegistry();
93
+ if (!registry) {
94
+ res.status(501).json({ error: 'Multi-tenant not enabled' });
95
+ return;
96
+ }
97
+ const { organizationId } = req.body;
98
+ if (!organizationId) {
99
+ res.status(400).json({ error: 'Missing organizationId' });
100
+ return;
101
+ }
102
+ const success = await registry.unregister(organizationId);
103
+ res.json({ success, message: success ? 'Unregistered successfully' : 'Organization not found' });
104
+ }
105
+ catch (error) {
106
+ const message = error instanceof Error ? error.message : 'Unknown error';
107
+ console.error('Unregister error:', error);
108
+ res.status(500).json({ error: 'Unregister failed', message });
109
+ }
110
+ });
111
+ // Stats endpoint (admin)
112
+ this.app.get('/stats', async (req, res) => {
113
+ try {
114
+ const registry = this.plugin.getRegistry();
115
+ if (!registry) {
116
+ res.status(501).json({ error: 'Multi-tenant not enabled' });
117
+ return;
118
+ }
119
+ const stats = await registry.getStats();
120
+ res.json(stats);
121
+ }
122
+ catch (error) {
123
+ const message = error instanceof Error ? error.message : 'Unknown error';
124
+ res.status(500).json({ error: message });
125
+ }
126
+ });
127
+ }
128
+ // Manifest endpoint
129
+ this.app.get('/manifest.json', (req, res) => {
130
+ try {
131
+ const endpoint = this.getBaseUrl();
132
+ const manifest = this.plugin.generateManifest({ endpoint });
133
+ res.json(manifest);
134
+ }
135
+ catch (error) {
136
+ const message = error instanceof Error ? error.message : 'Unknown error';
137
+ res.status(500).json({ error: message });
138
+ }
139
+ });
140
+ // Transform endpoints
141
+ const allTransforms = this.plugin.getAllTransforms();
142
+ for (const transform of allTransforms) {
143
+ this.app.post(`/${transform.id}`, async (req, res) => {
144
+ try {
145
+ await this.handleTransform(transform.id, req, res);
146
+ }
147
+ catch (error) {
148
+ const message = error instanceof Error ? error.message : 'Unknown error';
149
+ console.error(`Transform ${transform.id} error:`, error);
150
+ res.status(500).json({
151
+ error: 'Transform execution failed',
152
+ message,
153
+ });
154
+ }
155
+ });
156
+ }
157
+ // 404 handler
158
+ this.app.use((req, res) => {
159
+ res.status(404).json({
160
+ error: 'Not found',
161
+ path: req.path,
162
+ });
163
+ });
164
+ }
165
+ /**
166
+ * Handler per transform
167
+ */
168
+ async handleTransform(transformId, req, res) {
169
+ const body = req.body;
170
+ if (!body.input) {
171
+ res.status(400).json({ error: 'Missing input' });
172
+ return;
173
+ }
174
+ // Estrai organization ID dall'header (per multi-tenancy)
175
+ const organizationId = req.headers['x-organization-id'];
176
+ // Se il plugin è multi-tenant, organizationId è obbligatorio
177
+ if (this.plugin.isMultiTenant() && !organizationId) {
178
+ res.status(400).json({ error: 'Missing X-Organization-Id header for multi-tenant plugin' });
179
+ return;
180
+ }
181
+ // Aggiungi organizationId all'input
182
+ if (organizationId) {
183
+ body.input.organizationId = organizationId;
184
+ }
185
+ const isAsync = this.plugin.isAsyncTransform(transformId);
186
+ if (isAsync) {
187
+ // Transform asincrona
188
+ if (!body.callbackUrl) {
189
+ res.status(400).json({ error: 'Missing callbackUrl for async transform' });
190
+ return;
191
+ }
192
+ const result = await this.plugin.executeAsyncTransform(transformId, body.input, body.callbackUrl, organizationId);
193
+ const response = {
194
+ async: true,
195
+ jobId: result.jobId,
196
+ estimatedTime: result.estimatedTime,
197
+ message: 'Job queued for processing',
198
+ };
199
+ res.json(response);
200
+ }
201
+ else {
202
+ // Transform sincrona
203
+ const result = await this.plugin.executeTransform(transformId, body.input);
204
+ const response = {
205
+ async: false,
206
+ result,
207
+ };
208
+ res.json(response);
209
+ }
210
+ }
211
+ /**
212
+ * Ottieni base URL del server
213
+ */
214
+ getBaseUrl() {
215
+ const protocol = this.options.https ? 'https' : 'http';
216
+ const host = this.options.host || 'localhost';
217
+ const port = this.options.port;
218
+ return `${protocol}://${host}:${port}`;
219
+ }
220
+ /**
221
+ * Avvia il server
222
+ */
223
+ async start() {
224
+ return new Promise((resolve, reject) => {
225
+ try {
226
+ if (this.options.https) {
227
+ // HTTPS server
228
+ const httpsOptions = {
229
+ key: fs_1.default.readFileSync(this.options.https.key),
230
+ cert: fs_1.default.readFileSync(this.options.https.cert),
231
+ };
232
+ this.server = https_1.default.createServer(httpsOptions, this.app);
233
+ }
234
+ else {
235
+ // HTTP server
236
+ this.server = this.app.listen(this.options.port, this.options.host || '0.0.0.0');
237
+ }
238
+ this.server.listen(this.options.port, this.options.host || '0.0.0.0', () => {
239
+ console.log(`🚀 Server listening on ${this.getBaseUrl()}`);
240
+ resolve();
241
+ });
242
+ this.server.on('error', (error) => {
243
+ console.error('Server error:', error);
244
+ reject(error);
245
+ });
246
+ }
247
+ catch (error) {
248
+ reject(error);
249
+ }
250
+ });
251
+ }
252
+ /**
253
+ * Ferma il server
254
+ */
255
+ async stop() {
256
+ return new Promise((resolve, reject) => {
257
+ if (!this.server) {
258
+ resolve();
259
+ return;
260
+ }
261
+ this.server.close((err) => {
262
+ if (err) {
263
+ reject(err);
264
+ }
265
+ else {
266
+ console.log('✅ Server stopped');
267
+ resolve();
268
+ }
269
+ });
270
+ });
271
+ }
272
+ /**
273
+ * Ottieni Express app (per customizzazione)
274
+ */
275
+ getApp() {
276
+ return this.app;
277
+ }
278
+ }
279
+ exports.ExpressServer = ExpressServer;
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@relazio/plugin-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for building external plugins for Relazio",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "prepublishOnly": "npm run build",
11
+ "test": "vitest",
12
+ "lint": "eslint src --ext .ts"
13
+ },
14
+ "keywords": [
15
+ "relazio",
16
+ "osint",
17
+ "plugin",
18
+ "sdk",
19
+ "external-plugin"
20
+ ],
21
+ "author": "Relazio Team",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/relazio/plugin-sdk.git"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "express": "^4.18.2",
37
+ "zod": "^3.22.4"
38
+ },
39
+ "devDependencies": {
40
+ "@types/express": "^4.17.21",
41
+ "@types/node": "^20.10.6",
42
+ "@typescript-eslint/eslint-plugin": "^6.17.0",
43
+ "@typescript-eslint/parser": "^6.17.0",
44
+ "eslint": "^8.56.0",
45
+ "typescript": "^5.3.3",
46
+ "vitest": "^1.1.0"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }
52
+