@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 +61 -0
- package/express.js +1 -0
- package/index.js +1 -0
- package/package.json +25 -0
- package/src/client.js +54 -0
- package/src/express.js +51 -0
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 };
|