@piplfy/widget 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/README.md +190 -0
- package/bin/cli.js +18 -0
- package/lib/index.js +84 -0
- package/lib/resolve.js +51 -0
- package/lib/schemas.js +28 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Piplfy Widget
|
|
2
|
+
|
|
3
|
+
Native WebSocket bridge that runs as a sidecar process alongside your Node.js application. Handles real-time communication with the Piplfy platform through an embedded Rust binary — no native compilation required on install.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install piplfy-widget
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The correct binary for your OS and architecture is installed automatically. No extra steps needed.
|
|
12
|
+
|
|
13
|
+
### Supported platforms
|
|
14
|
+
|
|
15
|
+
| Platform | Architecture | Package |
|
|
16
|
+
| -------- | ------------ | ------- |
|
|
17
|
+
| Linux | x64 | `piplfy-widget-linux-x64` |
|
|
18
|
+
| Windows | x64 | `piplfy-widget-win32-x64` |
|
|
19
|
+
|
|
20
|
+
> macOS and ARM support coming soon.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { startPwidget } from "piplfy-widget";
|
|
26
|
+
|
|
27
|
+
const widget = await startPwidget({
|
|
28
|
+
onLog: (line) => console.log("[pwidget]", line),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// The binary is now running and listening for WebSocket connections.
|
|
32
|
+
// When you're done:
|
|
33
|
+
widget.kill();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### `startPwidget(options?)`
|
|
39
|
+
|
|
40
|
+
Starts the native binary as a child process. Returns a Promise that resolves once the process is ready (emits `"listening on"` to stdout).
|
|
41
|
+
|
|
42
|
+
**Options:**
|
|
43
|
+
|
|
44
|
+
| Parameter | Type | Default | Description |
|
|
45
|
+
| --------- | ---- | ------- | ----------- |
|
|
46
|
+
| `timeout` | `number` | `5000` | Max milliseconds to wait for startup before rejecting |
|
|
47
|
+
| `env` | `Record<string, string>` | `{}` | Extra environment variables passed to the binary |
|
|
48
|
+
| `onLog` | `(data: string) => void` | — | Called on every stdout/stderr chunk |
|
|
49
|
+
| `onExit` | `(code: number \| null) => void` | — | Called when the process exits |
|
|
50
|
+
|
|
51
|
+
**Returns:** `Promise<{ kill: () => void, process: ChildProcess }>`
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
const widget = await startPwidget({
|
|
55
|
+
timeout: 8000,
|
|
56
|
+
env: {
|
|
57
|
+
PORT: "9090",
|
|
58
|
+
PWIDGET_SECRET: process.env.PWIDGET_SECRET,
|
|
59
|
+
},
|
|
60
|
+
onLog: (line) => console.log("[pwidget]", line),
|
|
61
|
+
onExit: (code) => {
|
|
62
|
+
if (code !== 0) console.error("pwidget crashed with code", code);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `resolveBinaryPath()`
|
|
68
|
+
|
|
69
|
+
Returns the absolute path to the native binary for the current platform. Useful if you want to manage the process yourself.
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import { resolveBinaryPath } from "piplfy-widget";
|
|
73
|
+
|
|
74
|
+
const binaryPath = resolveBinaryPath();
|
|
75
|
+
// => "/path/to/node_modules/piplfy-widget-linux-x64/bin/pwidget"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## CLI
|
|
79
|
+
|
|
80
|
+
You can also run the binary directly from the command line:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx piplfy-widget
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Zod schemas
|
|
87
|
+
|
|
88
|
+
The package exports Zod validation schemas that match the data structures expected by the binary. Zod is an optional peer dependency — if you don't use the schemas, you don't need to install it.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install zod
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
import { userContextSchema } from "piplfy-widget/schemas";
|
|
96
|
+
|
|
97
|
+
const result = userContextSchema.safeParse(payload);
|
|
98
|
+
|
|
99
|
+
if (!result.success) {
|
|
100
|
+
console.error(result.error.issues);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// result.data is fully typed
|
|
105
|
+
console.log(result.data.email);
|
|
106
|
+
console.log(result.data.organization.domain);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The `userContextSchema` validates the following shape:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
{
|
|
113
|
+
id: string
|
|
114
|
+
username: string | null
|
|
115
|
+
email: string // validated as email
|
|
116
|
+
profile: {
|
|
117
|
+
firstname: string
|
|
118
|
+
lastname: string | null
|
|
119
|
+
phone: string | null
|
|
120
|
+
}
|
|
121
|
+
organization: {
|
|
122
|
+
name: string
|
|
123
|
+
domain: string
|
|
124
|
+
public_domain: string | null
|
|
125
|
+
description: string | null
|
|
126
|
+
country_id: string | null // validated as UUID
|
|
127
|
+
sector_id: string | null // validated as UUID
|
|
128
|
+
size_id: string | null // validated as UUID
|
|
129
|
+
target_countries: string[] | null
|
|
130
|
+
target_sectors: string[] | null
|
|
131
|
+
target_sizes: string[] | null
|
|
132
|
+
target_tags: string[] | null
|
|
133
|
+
logo_url: string | null // validated as URL
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Usage with Express
|
|
139
|
+
|
|
140
|
+
A common pattern is to proxy WebSocket connections from your Express server to the native binary:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
import express from "express";
|
|
144
|
+
import http from "http";
|
|
145
|
+
import httpProxy from "http-proxy";
|
|
146
|
+
import { startPwidget } from "piplfy-widget";
|
|
147
|
+
|
|
148
|
+
const app = express();
|
|
149
|
+
const server = http.createServer(app);
|
|
150
|
+
|
|
151
|
+
const proxy = httpProxy.createProxyServer({
|
|
152
|
+
target: "http://localhost:8080",
|
|
153
|
+
ws: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await startPwidget({
|
|
157
|
+
env: { PORT: "8080" },
|
|
158
|
+
onLog: (line) => console.log("[pwidget]", line),
|
|
159
|
+
onExit: (code) => {
|
|
160
|
+
console.error("pwidget exited unexpectedly:", code);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
app.get("/health", (req, res) => res.json({ status: "ok" }));
|
|
166
|
+
|
|
167
|
+
server.on("upgrade", (req, socket, head) => {
|
|
168
|
+
if (req.url === "/ws") {
|
|
169
|
+
proxy.ws(req, socket, head);
|
|
170
|
+
} else {
|
|
171
|
+
socket.destroy();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
server.listen(3000, () => {
|
|
176
|
+
console.log("Server listening on :3000");
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Troubleshooting
|
|
181
|
+
|
|
182
|
+
**`piplfy-widget: plataforma "X" no soportada`** — Your OS/arch combination doesn't have a prebuilt binary yet. See the supported platforms table above.
|
|
183
|
+
|
|
184
|
+
**`pwidget failed to start within 5000ms`** — The binary didn't emit `"listening on"` in time. Try increasing the `timeout` option or check the logs with `onLog`.
|
|
185
|
+
|
|
186
|
+
**`no se encontró el paquete "piplfy-widget-..."`** — The platform-specific package didn't install. Run `npm install` again, or install it explicitly: `npm install piplfy-widget-linux-x64`.
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { resolveBinaryPath } from "../lib/resolve.js";
|
|
5
|
+
|
|
6
|
+
const binary = resolveBinaryPath();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
execFileSync(binary, process.argv.slice(2), {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
});
|
|
12
|
+
} catch (err) {
|
|
13
|
+
if (err.status !== null) {
|
|
14
|
+
process.exit(err.status);
|
|
15
|
+
}
|
|
16
|
+
console.error(`Failed to run piplfy-widget: ${err.message}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveBinaryPath } from "./resolve.js";
|
|
3
|
+
|
|
4
|
+
export { resolveBinaryPath } from "./resolve.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Inicia el binario pwidget como proceso hijo.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} [options]
|
|
10
|
+
* @param {number} [options.timeout=5000] - Tiempo máximo de espera para el arranque (ms)
|
|
11
|
+
* @param {Record<string, string>} [options.env] - Variables de entorno extra para el proceso
|
|
12
|
+
* @param {(data: string) => void} [options.onLog] - Callback para cada línea de log
|
|
13
|
+
* @param {(code: number | null) => void} [options.onExit] - Callback al terminar el proceso
|
|
14
|
+
* @returns {Promise<{ kill: () => void, process: import("child_process").ChildProcess }>}
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```js
|
|
18
|
+
* import { startPwidget } from "piplfy-widget";
|
|
19
|
+
*
|
|
20
|
+
* const widget = await startPwidget({
|
|
21
|
+
* timeout: 8000,
|
|
22
|
+
* env: { PORT: "9090" },
|
|
23
|
+
* onLog: (line) => console.log("[pwidget]", line),
|
|
24
|
+
* onExit: (code) => console.log("Exited with", code),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Cuando quieras pararlo:
|
|
28
|
+
* widget.kill();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function startPwidget(options = {}) {
|
|
32
|
+
const { timeout = 5000, env, onLog, onExit } = options;
|
|
33
|
+
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const binaryPath = resolveBinaryPath();
|
|
36
|
+
|
|
37
|
+
const child = spawn(binaryPath, [], {
|
|
38
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
env: { ...process.env, ...env },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let started = false;
|
|
44
|
+
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
if (!started) {
|
|
47
|
+
child.kill();
|
|
48
|
+
reject(new Error(`pwidget failed to start within ${timeout}ms`));
|
|
49
|
+
}
|
|
50
|
+
}, timeout);
|
|
51
|
+
|
|
52
|
+
child.stdout?.on("data", (data) => {
|
|
53
|
+
const text = data.toString();
|
|
54
|
+
onLog?.(text);
|
|
55
|
+
|
|
56
|
+
if (!started && text.includes("listening on")) {
|
|
57
|
+
started = true;
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
resolve({ kill: () => child.kill(), process: child });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
child.stderr?.on("data", (data) => {
|
|
64
|
+
onLog?.(data.toString());
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
child.on("error", (err) => {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
if (!started) {
|
|
70
|
+
reject(new Error(`Failed to start pwidget: ${err.message}`));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
child.on("exit", (code) => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
onExit?.(code);
|
|
77
|
+
if (!started) {
|
|
78
|
+
reject(
|
|
79
|
+
new Error(`pwidget exited with code ${code} before starting`),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
package/lib/resolve.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const BINARY_NAME = "pwidget";
|
|
6
|
+
|
|
7
|
+
const PLATFORM_PACKAGES = {
|
|
8
|
+
"linux-x64": "@piplfy/widget-linux-x64",
|
|
9
|
+
"win32-x64": "@piplfy/widget-win32-x64",
|
|
10
|
+
// "darwin-x64": "@piplfy/widget-darwin-x64",
|
|
11
|
+
// "darwin-arm64": "@piplfy/widget-darwin-arm64",
|
|
12
|
+
// "linux-arm64": "@piplfy/widget-linux-arm64",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resuelve la ruta al binario nativo para la plataforma actual.
|
|
17
|
+
* Busca el paquete instalado vía optionalDependencies.
|
|
18
|
+
* @returns {string} Ruta absoluta al binario
|
|
19
|
+
*/
|
|
20
|
+
export function resolveBinaryPath() {
|
|
21
|
+
const platform = os.platform();
|
|
22
|
+
const arch = os.arch();
|
|
23
|
+
const key = `${platform}-${arch}`;
|
|
24
|
+
|
|
25
|
+
const packageName = PLATFORM_PACKAGES[key];
|
|
26
|
+
|
|
27
|
+
if (!packageName) {
|
|
28
|
+
const supported = Object.keys(PLATFORM_PACKAGES).join(", ");
|
|
29
|
+
throw new Error(
|
|
30
|
+
`@piplfy/widget: plataforma "${key}" no soportada.\n` +
|
|
31
|
+
`Plataformas soportadas: ${supported}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ext = platform === "win32" ? ".exe" : "";
|
|
36
|
+
const binaryFile = `${BINARY_NAME}${ext}`;
|
|
37
|
+
|
|
38
|
+
const require = createRequire(import.meta.url);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const packageDir = path.dirname(
|
|
42
|
+
require.resolve(`${packageName}/package.json`),
|
|
43
|
+
);
|
|
44
|
+
return path.join(packageDir, "bin", binaryFile);
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`@piplfy/widget: no se encontró el paquete "${packageName}".\n` +
|
|
48
|
+
`Ejecutá: npm install ${packageName}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
package/lib/schemas.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
export const userContextSchema = z.object({
|
|
4
|
+
id: z.string(),
|
|
5
|
+
email: z.string(),
|
|
6
|
+
username: z.string().nullish(),
|
|
7
|
+
profile: z.object({
|
|
8
|
+
firstname: z.string(),
|
|
9
|
+
lastname: z.string().nullish(),
|
|
10
|
+
phone: z.string().nullish()
|
|
11
|
+
}),
|
|
12
|
+
organization: z.object({
|
|
13
|
+
name: z.string(),
|
|
14
|
+
domain: z.string(),
|
|
15
|
+
public_domain: z.string().nullish(),
|
|
16
|
+
description: z.string().nullish(),
|
|
17
|
+
country_id: z.string().nullish(),
|
|
18
|
+
sector_id: z.string().nullish(),
|
|
19
|
+
size_id: z.string().nullish(),
|
|
20
|
+
target_countries: z.array(z.string()).nullish(),
|
|
21
|
+
target_sectors: z.array(z.string()).nullish(),
|
|
22
|
+
target_sizes: z.array(z.string()).nullish(),
|
|
23
|
+
target_tags: z.array(z.string()).nullish(),
|
|
24
|
+
logo_url: z.string().nullish()
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** @typedef {z.infer<typeof userContextSchema>} UserContext */
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@piplfy/widget",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Piplfy Widget — native WebSocket bridge for Node.js applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./schemas": "./lib/schemas.js"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"piplfy-widget": "./bin/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"lib",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"optionalDependencies": {
|
|
20
|
+
"@piplfy/widget-linux-x64": "0.0.1",
|
|
21
|
+
"@piplfy/widget-win32-x64": "0.0.1"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"zod": "^3.0.0 || ^4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependenciesMeta": {
|
|
27
|
+
"zod": {
|
|
28
|
+
"optional": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"piplfy",
|
|
33
|
+
"widget",
|
|
34
|
+
"websocket",
|
|
35
|
+
"native",
|
|
36
|
+
"binary"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
}
|
|
42
|
+
}
|