@swarm.ing/pieui 2.1.6 → 2.2.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.
Files changed (66) hide show
  1. package/dist/components/PieCard/index.d.ts.map +1 -1
  2. package/dist/components/index.esm.js +1 -1
  3. package/dist/components/index.js +1 -1
  4. package/dist/index.esm.js +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/native/index.esm.js +2 -2
  7. package/dist/native/index.js +2 -2
  8. package/dist/server/card.d.ts +28 -0
  9. package/dist/server/card.d.ts.map +1 -0
  10. package/dist/server/cards/AjaxGroupCard.d.ts +8 -0
  11. package/dist/server/cards/AjaxGroupCard.d.ts.map +1 -0
  12. package/dist/server/cards/HTMLEmbedCard.d.ts +6 -0
  13. package/dist/server/cards/HTMLEmbedCard.d.ts.map +1 -0
  14. package/dist/server/cards/HiddenCard.d.ts +6 -0
  15. package/dist/server/cards/HiddenCard.d.ts.map +1 -0
  16. package/dist/server/cards/OneOfCard.d.ts +8 -0
  17. package/dist/server/cards/OneOfCard.d.ts.map +1 -0
  18. package/dist/server/cards/UnionCard.d.ts +8 -0
  19. package/dist/server/cards/UnionCard.d.ts.map +1 -0
  20. package/dist/server/cards/index.d.ts +6 -0
  21. package/dist/server/cards/index.d.ts.map +1 -0
  22. package/dist/server/form.d.ts +5 -0
  23. package/dist/server/form.d.ts.map +1 -0
  24. package/dist/server/index.d.ts +9 -0
  25. package/dist/server/index.d.ts.map +1 -0
  26. package/dist/server/index.esm.js +382 -0
  27. package/dist/server/index.js +427 -0
  28. package/dist/server/page.d.ts +26 -0
  29. package/dist/server/page.d.ts.map +1 -0
  30. package/dist/server/realtime.d.ts +13 -0
  31. package/dist/server/realtime.d.ts.map +1 -0
  32. package/dist/server/types.d.ts +28 -0
  33. package/dist/server/types.d.ts.map +1 -0
  34. package/dist/server/web.d.ts +23 -0
  35. package/dist/server/web.d.ts.map +1 -0
  36. package/dist/telegram/index.esm.js +1 -1
  37. package/dist/telegram/index.js +1 -1
  38. package/dist/tests/centrifugeSubscription.test.d.ts +2 -0
  39. package/dist/tests/centrifugeSubscription.test.d.ts.map +1 -0
  40. package/dist/tests/server/card-fill.test.d.ts +2 -0
  41. package/dist/tests/server/card-fill.test.d.ts.map +1 -0
  42. package/dist/tests/server/card-generate.test.d.ts +2 -0
  43. package/dist/tests/server/card-generate.test.d.ts.map +1 -0
  44. package/dist/tests/server/cards.test.d.ts +2 -0
  45. package/dist/tests/server/cards.test.d.ts.map +1 -0
  46. package/dist/tests/server/e2e.test.d.ts +2 -0
  47. package/dist/tests/server/e2e.test.d.ts.map +1 -0
  48. package/dist/tests/server/entry.test.d.ts +2 -0
  49. package/dist/tests/server/entry.test.d.ts.map +1 -0
  50. package/dist/tests/server/form.test.d.ts +2 -0
  51. package/dist/tests/server/form.test.d.ts.map +1 -0
  52. package/dist/tests/server/page.test.d.ts +2 -0
  53. package/dist/tests/server/page.test.d.ts.map +1 -0
  54. package/dist/tests/server/realtime.test.d.ts +2 -0
  55. package/dist/tests/server/realtime.test.d.ts.map +1 -0
  56. package/dist/tests/server/web-ajax.test.d.ts +2 -0
  57. package/dist/tests/server/web-ajax.test.d.ts.map +1 -0
  58. package/dist/tests/server/web-content.test.d.ts +2 -0
  59. package/dist/tests/server/web-content.test.d.ts.map +1 -0
  60. package/dist/tests/server/web-process.test.d.ts +2 -0
  61. package/dist/tests/server/web-process.test.d.ts.map +1 -0
  62. package/dist/tests/server/web-token.test.d.ts +2 -0
  63. package/dist/tests/server/web-token.test.d.ts.map +1 -0
  64. package/dist/util/centrifugeSubscription.d.ts +18 -0
  65. package/dist/util/centrifugeSubscription.d.ts.map +1 -0
  66. package/package.json +28 -3
@@ -0,0 +1,382 @@
1
+ // src/server/card.ts
2
+ function isCardRecord(v) {
3
+ if (typeof v !== "object" || v === null || Array.isArray(v))
4
+ return false;
5
+ const vals = Object.values(v);
6
+ return vals.length > 0 && vals.every((x) => x instanceof Card);
7
+ }
8
+
9
+ class Card {
10
+ card = this.constructor.name;
11
+ constructor(props) {
12
+ if (props)
13
+ Object.assign(this, props);
14
+ }
15
+ generate() {
16
+ const data = {};
17
+ const children = {};
18
+ for (const key of Object.keys(this)) {
19
+ if (key === "card")
20
+ continue;
21
+ const value = this[key];
22
+ if (value instanceof Card) {
23
+ children[key] = value.generate();
24
+ } else if (Array.isArray(value) && value.length > 0 && value.every((v) => v instanceof Card)) {
25
+ children[key] = value.map((v) => v.generate());
26
+ } else if (isCardRecord(value)) {
27
+ children[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [
28
+ k,
29
+ v.generate()
30
+ ]));
31
+ } else {
32
+ data[key] = value;
33
+ }
34
+ }
35
+ return { ...children, card: this.card, data };
36
+ }
37
+ inputChildLoc() {
38
+ const acc = {};
39
+ const visit = (card) => {
40
+ const name = card.name;
41
+ if (card instanceof InputCard && typeof name === "string") {
42
+ acc[name] = card;
43
+ }
44
+ for (const key of Object.keys(card)) {
45
+ const v = card[key];
46
+ if (v instanceof Card)
47
+ visit(v);
48
+ else if (Array.isArray(v))
49
+ v.forEach((x) => x instanceof Card && visit(x));
50
+ else if (v && typeof v === "object")
51
+ Object.values(v).forEach((x) => x instanceof Card && visit(x));
52
+ }
53
+ };
54
+ visit(this);
55
+ return acc;
56
+ }
57
+ fill(data) {
58
+ const loc = this.inputChildLoc();
59
+ for (const [name, card] of Object.entries(loc)) {
60
+ if (name in data) {
61
+ card.value = data[name];
62
+ }
63
+ }
64
+ return this.generate();
65
+ }
66
+ createEvent(method, data = {}) {
67
+ const name = this.name;
68
+ if (typeof name !== "string") {
69
+ throw new Error("Card.createEvent requires a string `name`");
70
+ }
71
+ const self = this;
72
+ const io = self.useSocketioSupport || self.useCentrifugeSupport || self.useMittSupport;
73
+ if (!io) {
74
+ throw new Error("Card.createEvent requires an IO support flag enabled");
75
+ }
76
+ return { name: `pie${method}_${name}`, data };
77
+ }
78
+ }
79
+
80
+ class InputCard extends Card {
81
+ parse(raw) {
82
+ return raw;
83
+ }
84
+ }
85
+ // src/server/page.ts
86
+ class AsyncPage {
87
+ fields;
88
+ isTyped;
89
+ ajaxPost = {};
90
+ ajaxGet = {};
91
+ publisher;
92
+ constructor(isTyped = false) {
93
+ this.isTyped = isTyped;
94
+ }
95
+ async getContent(_ctx = {}) {
96
+ if (this.fields)
97
+ return this.fields.fill({});
98
+ throw new Error("getContent not implemented and no `fields` set");
99
+ }
100
+ async process(_data) {
101
+ throw new Error("process not implemented");
102
+ }
103
+ registerAjax(pathname, fn, method = "POST") {
104
+ if (method === "POST")
105
+ this.ajaxPost[pathname] = fn;
106
+ else
107
+ this.ajaxGet[pathname] = fn;
108
+ }
109
+ setPublisher(p) {
110
+ this.publisher = p;
111
+ }
112
+ async emit(event, to) {
113
+ if (!this.publisher) {
114
+ throw new Error("no publisher configured (Web must call setPublisher)");
115
+ }
116
+ const events = Array.isArray(event) ? event : [event];
117
+ for (const ev of events)
118
+ await this.publisher(ev, to);
119
+ }
120
+ }
121
+ // src/server/web.ts
122
+ import express from "express";
123
+ import cookieParser from "cookie-parser";
124
+ import jwt from "jsonwebtoken";
125
+ import { createHash } from "node:crypto";
126
+
127
+ // src/server/form.ts
128
+ function form2dict(body) {
129
+ const out = {};
130
+ for (const [k, v] of Object.entries(body ?? {})) {
131
+ out[k] = Array.isArray(v) && v.length === 1 ? v[0] : v;
132
+ }
133
+ return out;
134
+ }
135
+ function aggregate(data, rule) {
136
+ if (rule !== "by_underscore")
137
+ return data;
138
+ const out = {};
139
+ for (const [k, v] of Object.entries(data)) {
140
+ const idx = k.indexOf("__");
141
+ if (idx === -1) {
142
+ out[k] = v;
143
+ } else {
144
+ const parent = k.slice(0, idx);
145
+ const child = k.slice(idx + 2);
146
+ const bucket = out[parent] ??= {};
147
+ bucket[child] = v;
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+
153
+ // src/server/realtime.ts
154
+ async function publishToCentrifuge(opts, fetchFn = fetch) {
155
+ const res = await fetchFn(`${opts.url.replace(/\/$/, "")}/api/publish`, {
156
+ method: "POST",
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ "X-API-Key": opts.apiKey
160
+ },
161
+ body: JSON.stringify({ channel: opts.channel, data: opts.data })
162
+ });
163
+ if (!res.ok) {
164
+ throw new Error(`centrifuge publish failed: ${res.status}`);
165
+ }
166
+ }
167
+
168
+ // src/server/web.ts
169
+ function escapeRegExp(s) {
170
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
171
+ }
172
+
173
+ class Web {
174
+ pages;
175
+ opts;
176
+ constructor(pages, opts = {}) {
177
+ this.pages = pages;
178
+ const adminSubdomain = opts.adminSubdomain ?? "/";
179
+ if (!adminSubdomain.startsWith("/") || !adminSubdomain.endsWith("/")) {
180
+ throw new Error('adminSubdomain must start and end with "/"');
181
+ }
182
+ this.opts = { ...opts, adminSubdomain };
183
+ this.wirePublishers();
184
+ }
185
+ wirePublishers() {
186
+ if (!this.opts.useCentrifugeSupport || !this.opts.centrifugeUrl || !this.opts.centrifugeApiKey) {
187
+ return;
188
+ }
189
+ for (const entry of Object.values(this.pages)) {
190
+ if (typeof entry === "string")
191
+ continue;
192
+ entry.setPublisher(async (ev, to) => {
193
+ const channel = to ? `${ev.name}_${to}` : ev.name;
194
+ await publishToCentrifuge({
195
+ url: this.opts.centrifugeUrl,
196
+ apiKey: this.opts.centrifugeApiKey,
197
+ channel,
198
+ data: ev.data
199
+ });
200
+ });
201
+ }
202
+ }
203
+ resolvePage(pathname) {
204
+ let key = pathname.replace(/^\/+/, "");
205
+ let hops = 0;
206
+ while (hops++ < 10) {
207
+ const entry = this.pages[key];
208
+ if (entry === undefined)
209
+ return null;
210
+ if (typeof entry === "string") {
211
+ key = entry;
212
+ continue;
213
+ }
214
+ return entry;
215
+ }
216
+ return null;
217
+ }
218
+ contextFromRequest(req) {
219
+ const ctx = {};
220
+ for (const k of this.opts.cookieKeys ?? []) {
221
+ const c = req.cookies?.[k];
222
+ if (c !== undefined)
223
+ ctx[k] = c;
224
+ }
225
+ Object.assign(ctx, req.query);
226
+ return ctx;
227
+ }
228
+ collectAjax(kind) {
229
+ const map = {};
230
+ for (const entry of Object.values(this.pages)) {
231
+ if (typeof entry === "string")
232
+ continue;
233
+ Object.assign(map, entry[kind]);
234
+ }
235
+ return map;
236
+ }
237
+ getApp() {
238
+ const app = express();
239
+ app.use(express.urlencoded({ extended: true }));
240
+ app.use(express.json());
241
+ app.use(cookieParser());
242
+ if (this.opts.enableCors)
243
+ app.use(this.corsMiddleware());
244
+ const sub = this.opts.adminSubdomain;
245
+ const contentPrefix = `${sub}api/content/`;
246
+ const processPrefix = `${sub}api/process/`;
247
+ const ajaxPrefix = `${sub}api/ajax_content/`;
248
+ app.get(new RegExp("^" + escapeRegExp(contentPrefix)), async (req, res) => {
249
+ const pathname = decodeURIComponent(req.path.slice(contentPrefix.length));
250
+ const page = this.resolvePage(pathname);
251
+ if (!page) {
252
+ res.status(404).json({ error: "page not found" });
253
+ return;
254
+ }
255
+ try {
256
+ res.json(await page.getContent(this.contextFromRequest(req)));
257
+ } catch (e) {
258
+ res.status(500).json({ error: String(e) });
259
+ }
260
+ });
261
+ app.post(new RegExp("^" + escapeRegExp(processPrefix)), async (req, res) => {
262
+ const pathname = decodeURIComponent(req.path.slice(processPrefix.length));
263
+ const page = this.resolvePage(pathname);
264
+ if (!page) {
265
+ res.status(404).json({ error: "page not found" });
266
+ return;
267
+ }
268
+ const data = aggregate(form2dict(req.body), this.opts.aggregationRule);
269
+ try {
270
+ const result = await page.process(data);
271
+ if (typeof result !== "string") {
272
+ if (!res.headersSent)
273
+ res.status(204).end();
274
+ return;
275
+ }
276
+ let url = result;
277
+ if (url.startsWith("/")) {
278
+ url = sub + url.slice(1);
279
+ if (this.opts.disableServing && this.opts.servingUrl) {
280
+ url = this.opts.servingUrl + url;
281
+ }
282
+ }
283
+ res.redirect(303, url);
284
+ } catch (e) {
285
+ res.status(500).json({ error: String(e) });
286
+ }
287
+ });
288
+ const postAjax = this.collectAjax("ajaxPost");
289
+ const getAjax = this.collectAjax("ajaxGet");
290
+ app.post(new RegExp("^" + escapeRegExp(ajaxPrefix)), async (req, res) => {
291
+ const pathname = "/" + req.path.slice(ajaxPrefix.length);
292
+ const fn = postAjax[pathname];
293
+ if (!fn) {
294
+ res.status(404).json({ error: "ajax handler not found" });
295
+ return;
296
+ }
297
+ const data = aggregate(form2dict(req.body), this.opts.aggregationRule);
298
+ res.json(await fn(data));
299
+ });
300
+ app.get(new RegExp("^" + escapeRegExp(ajaxPrefix)), async (req, res) => {
301
+ const pathname = "/" + req.path.slice(ajaxPrefix.length);
302
+ const fn = getAjax[pathname];
303
+ if (!fn) {
304
+ res.status(404).json({ error: "ajax handler not found" });
305
+ return;
306
+ }
307
+ const data = aggregate({ ...req.query }, this.opts.aggregationRule);
308
+ res.json(await fn(data));
309
+ });
310
+ app.get(`${sub}api/support/:name`, (req, res) => {
311
+ const name = req.params.name;
312
+ const supported = name === "socketio" ? !!this.opts.useSocketioSupport : name === "centrifuge" ? !!this.opts.useCentrifugeSupport : false;
313
+ res.json(supported);
314
+ });
315
+ app.get("/api/centrifuge/gen_token", (req, res) => {
316
+ const secret = this.opts.centrifugeHmacSecret;
317
+ if (!secret) {
318
+ res.status(403).json({ error: "no centrifuge secret" });
319
+ return;
320
+ }
321
+ const cookies = {};
322
+ for (const k of this.opts.cookieKeys ?? []) {
323
+ const v = req.cookies?.[k];
324
+ if (typeof v === "string")
325
+ cookies[k] = v;
326
+ }
327
+ const subId = this.opts.centrifugeSubFn?.(cookies) ?? createHash("sha256").update(JSON.stringify(cookies)).digest("hex");
328
+ const now = Math.floor(Date.now() / 1000);
329
+ const token = jwt.sign({ sub: subId, iat: now, exp: now + 3600 }, secret, { algorithm: "HS256" });
330
+ res.json({ token });
331
+ });
332
+ return app;
333
+ }
334
+ corsMiddleware() {
335
+ const origins = this.opts.corsOrigins;
336
+ return (req, res, next) => {
337
+ const origin = req.headers.origin;
338
+ if (!origins || origin && origins.includes(origin)) {
339
+ res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
340
+ }
341
+ res.setHeader("Access-Control-Allow-Credentials", "true");
342
+ res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
343
+ res.setHeader("Vary", "Origin");
344
+ const reqHeaders = req.headers["access-control-request-headers"];
345
+ res.setHeader("Access-Control-Allow-Headers", (Array.isArray(reqHeaders) ? reqHeaders.join(",") : reqHeaders) || "Content-Type");
346
+ if (req.method === "OPTIONS") {
347
+ res.sendStatus(204);
348
+ return;
349
+ }
350
+ next();
351
+ };
352
+ }
353
+ }
354
+ // src/server/cards/HiddenCard.ts
355
+ class HiddenCard extends InputCard {
356
+ }
357
+ // src/server/cards/UnionCard.ts
358
+ class UnionCard extends Card {
359
+ }
360
+ // src/server/cards/AjaxGroupCard.ts
361
+ class AjaxGroupCard extends Card {
362
+ }
363
+ // src/server/cards/OneOfCard.ts
364
+ class OneOfCard extends Card {
365
+ }
366
+ // src/server/cards/HTMLEmbedCard.ts
367
+ class HTMLEmbedCard extends Card {
368
+ }
369
+ export {
370
+ publishToCentrifuge,
371
+ form2dict,
372
+ aggregate,
373
+ Web,
374
+ UnionCard,
375
+ OneOfCard,
376
+ InputCard,
377
+ HiddenCard,
378
+ HTMLEmbedCard,
379
+ Card,
380
+ AsyncPage,
381
+ AjaxGroupCard
382
+ };