@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 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
+ }