@peepalytics/peepstick 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.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @peepalytics/peepstick
2
+
3
+ Drop-in ticket routing for Node apps, powered by PeepsTick. Configure the
4
+ Slack destination once in the PeepsTick dashboard — this package just
5
+ fetches your project's form schema and submits tickets to PeepsTick.
6
+ It never talks to Slack directly.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @peepalytics/peepstick
12
+ # or
13
+ pnpm add @peepalytics/peepstick
14
+ ```
15
+
16
+ It's a single published package — npm and pnpm both install the exact
17
+ same code, so pick whichever your project already uses.
18
+
19
+ Requires Node 18+ (uses the built-in `fetch`).
20
+
21
+ ## Core client (framework-agnostic)
22
+
23
+ ```js
24
+ const { getFormSchema, submitTicket } = require("@peepalytics/peepstick");
25
+
26
+ const config = { apiKey: process.env.PEEPSTICK_API_KEY, baseUrl: process.env.PEEPSTICK_BASE_URL };
27
+
28
+ const formSchema = await getFormSchema(config);
29
+ const ticket = await submitTicket({ ...config, fields: { title: "Login broken", priority: "High" } });
30
+ ```
31
+
32
+ Both throw `PeepsTickError` on failure (network issues, invalid API key,
33
+ missing required fields, etc).
34
+
35
+ ## Express integration
36
+
37
+ If you're on Express, mount a ready-made router so your frontend calls
38
+ your own server (keeping the API key out of the browser):
39
+
40
+ ```js
41
+ const express = require("express");
42
+ const { createTicketRouter } = require("@peepalytics/peepstick/express");
43
+
44
+ const app = express();
45
+
46
+ app.use(
47
+ "/peepstick",
48
+ createTicketRouter({
49
+ apiKey: process.env.PEEPSTICK_API_KEY,
50
+ baseUrl: process.env.PEEPSTICK_BASE_URL,
51
+ })
52
+ );
53
+ ```
54
+
55
+ This exposes:
56
+
57
+ - `GET /peepstick/form-schema` — proxies the live schema from PeepsTick
58
+ - `POST /peepstick/tickets` — proxies a submission (`{ "fields": {...} }`) to PeepsTick
59
+
60
+ Your frontend JS fetches `/peepstick/form-schema` to render the form and
61
+ posts to `/peepstick/tickets` on submit — same-origin, no API key exposed.
package/express.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./src/express");
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./src/client");
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@peepalytics/peepstick",
3
+ "version": "0.1.0",
4
+ "description": "Drop-in ticket routing for Node apps, powered by PeepsTick",
5
+ "main": "index.js",
6
+ "exports": {
7
+ ".": "./index.js",
8
+ "./express": "./express.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=18"
12
+ },
13
+ "peerDependencies": {
14
+ "express": "^5.2.1"
15
+ },
16
+ "peerDependenciesMeta": {
17
+ "express": {
18
+ "optional": true
19
+ }
20
+ },
21
+ "license": "MIT",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ }
25
+ }
package/src/client.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Thin HTTP client for the PeepsTick ticket-routing API.
3
+ *
4
+ * This is the only file that talks to the network. It never talks to
5
+ * Slack — it only ever calls your PeepsTick dashboard's API, which is the
6
+ * piece that owns the Slack webhook and does the actual posting.
7
+ */
8
+
9
+ class PeepsTickError extends Error {}
10
+
11
+ function headers(apiKey) {
12
+ return { "x-api-key": apiKey, "Content-Type": "application/json" };
13
+ }
14
+
15
+ async function parse(res) {
16
+ let data = {};
17
+ try {
18
+ data = await res.json();
19
+ } catch {
20
+ data = {};
21
+ }
22
+ if (!res.ok) {
23
+ throw new PeepsTickError(data.error || `PeepsTick API error (${res.status})`);
24
+ }
25
+ return data;
26
+ }
27
+
28
+ function request(baseUrl, path, options) {
29
+ const url = `${baseUrl.replace(/\/$/, "")}${path}`;
30
+ return fetch(url, options).catch((err) => {
31
+ throw new PeepsTickError(`Could not reach PeepsTick at ${url}: ${err.message}`);
32
+ });
33
+ }
34
+
35
+ /** Fetches the live form schema configured for this project. */
36
+ async function getFormSchema({ apiKey, baseUrl }) {
37
+ const res = await request(baseUrl, "/api/form-schema", { headers: headers(apiKey) });
38
+ const data = await parse(res);
39
+ return data.formSchema || [];
40
+ }
41
+
42
+ /** Submits ticket field values. PeepsTick validates, stores, and peepsticks to
43
+ * Slack server-side — this call never sees the webhook. */
44
+ async function submitTicket({ apiKey, baseUrl, fields }) {
45
+ const res = await request(baseUrl, "/api/tickets", {
46
+ method: "POST",
47
+ headers: headers(apiKey),
48
+ body: JSON.stringify({ fields }),
49
+ });
50
+ const data = await parse(res);
51
+ return data.ticket;
52
+ }
53
+
54
+ module.exports = { getFormSchema, submitTicket, PeepsTickError };
package/src/express.js ADDED
@@ -0,0 +1,51 @@
1
+ const express = require("express");
2
+ const { getFormSchema, submitTicket, PeepsTickError } = require("./client");
3
+
4
+ /**
5
+ * Creates an Express router with two routes:
6
+ * GET /form-schema — proxies PeepsTick's live form schema to your frontend
7
+ * POST /tickets — proxies a submission to PeepsTick
8
+ *
9
+ * Mount it under your own server so the browser only ever talks to your
10
+ * app, never to PeepsTick directly — the API key stays server-side.
11
+ *
12
+ * const { createTicketRouter } = require("peepstick/express");
13
+ * app.use("/peepstick", createTicketRouter({
14
+ * apiKey: process.env.PEEPSTICK_API_KEY,
15
+ * baseUrl: process.env.PEEPSTICK_BASE_URL,
16
+ * }));
17
+ */
18
+ function createTicketRouter({ apiKey, baseUrl }) {
19
+ if (!apiKey || !baseUrl) {
20
+ throw new Error("createTicketRouter requires both apiKey and baseUrl");
21
+ }
22
+
23
+ const router = express.Router();
24
+ router.use(express.json());
25
+
26
+ router.get("/form-schema", async (req, res) => {
27
+ try {
28
+ const formSchema = await getFormSchema({ apiKey, baseUrl });
29
+ res.json({ formSchema });
30
+ } catch (err) {
31
+ res.status(502).json({ error: errorMessage(err) });
32
+ }
33
+ });
34
+
35
+ router.post("/tickets", async (req, res) => {
36
+ try {
37
+ const ticket = await submitTicket({ apiKey, baseUrl, fields: req.body.fields || {} });
38
+ res.status(201).json({ ticket });
39
+ } catch (err) {
40
+ res.status(502).json({ error: errorMessage(err) });
41
+ }
42
+ });
43
+
44
+ return router;
45
+ }
46
+
47
+ function errorMessage(err) {
48
+ return err instanceof PeepsTickError ? err.message : "PeepsTick unreachable";
49
+ }
50
+
51
+ module.exports = { createTicketRouter };