@jitsu/js 0.0.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.
@@ -0,0 +1,23 @@
1
+ {
2
+ "0 debug pnpm:scope": {
3
+ "selected": 1,
4
+ "workspacePrefix": "/Users/ildarnurislamov/Projects/onetag"
5
+ },
6
+ "1 error pnpm": {
7
+ "code": "ELIFECYCLE",
8
+ "errno": "ENOENT",
9
+ "syscall": "spawn",
10
+ "file": "sh",
11
+ "pkgid": "@jitsu/js@0.0.0",
12
+ "stage": "build",
13
+ "script": "tsc -p . && rollup -c && cp src/jitsu.d.ts dist",
14
+ "pkgname": "@jitsu/js",
15
+ "err": {
16
+ "name": "pnpm",
17
+ "message": "@jitsu/js@0.0.0 build: `tsc -p . && rollup -c && cp src/jitsu.d.ts dist`\nspawn ENOENT",
18
+ "code": "ELIFECYCLE",
19
+ "stack": "pnpm: @jitsu/js@0.0.0 build: `tsc -p . && rollup -c && cp src/jitsu.d.ts dist`\nspawn ENOENT\n at ChildProcess.<anonymous> (/opt/homebrew/lib/node_modules/pnpm/dist/pnpm.cjs:93535:22)\n at ChildProcess.emit (node:events:527:28)\n at maybeClose (node:internal/child_process:1092:16)\n at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)"
20
+ }
21
+ },
22
+ "2 warn pnpm:global": " Local package.json exists, but node_modules missing, did you mean to install?"
23
+ }
@@ -0,0 +1,15 @@
1
+ @jitsu/js:build: cache hit, replaying output fd9a15b75ea61c02
2
+ @jitsu/js:build: 
3
+ @jitsu/js:build: > @jitsu/js@0.0.1 build /Users/ildarnurislamov/Projects/onetag/libs/jitsu-js
4
+ @jitsu/js:build: > tsc -p . && rollup -c && cp src/jitsu.d.ts dist
5
+ @jitsu/js:build: 
6
+ @jitsu/js:build: 
7
+ @jitsu/js:build: ./compiled/src/browser.js → dist/web/p.js.txt...
8
+ @jitsu/js:build: (!) Circular dependency
9
+ @jitsu/js:build: compiled/src/index.js -> compiled/src/analytics-plugin.js -> compiled/src/index.js
10
+ @jitsu/js:build: created dist/web/p.js.txt in 552ms
11
+ @jitsu/js:build: 
12
+ @jitsu/js:build: ./compiled/src/index.js → dist/jitsu.es.js, dist/jitsu.cjs.js...
13
+ @jitsu/js:build: (!) Circular dependency
14
+ @jitsu/js:build: compiled/src/index.js -> compiled/src/analytics-plugin.js -> compiled/src/index.js
15
+ @jitsu/js:build: created dist/jitsu.es.js, dist/jitsu.cjs.js in 308ms
@@ -0,0 +1,5 @@
1
+ @jitsu/js:clean: cache hit, replaying output 6cc5edb43189bde9
2
+ @jitsu/js:clean: 
3
+ @jitsu/js:clean: > @jitsu/js@0.0.1 clean /Users/ildarnurislamov/Projects/onetag/libs/jitsu-js
4
+ @jitsu/js:clean: > rm -rf ./dist
5
+ @jitsu/js:clean: 
@@ -0,0 +1,84 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { createServer, SimpleSyrup } from "../simple-syrup";
4
+ import process from "process";
5
+ import { AnalyticsClientEvent, AnalyticsInterface } from "@jitsu/types/analytics";
6
+
7
+ const jitsuAnalytics = require("../../dist/jitsu.cjs.js").jitsuAnalytics;
8
+ const fetchImpl = require("node-fetch-commonjs");
9
+
10
+ describe("Test Jitsu NodeJS client", () => {
11
+ let server: SimpleSyrup;
12
+
13
+ let requestLog: { type: string; body: AnalyticsClientEvent }[] = [];
14
+
15
+ const startServer = async () => {
16
+ server = await createServer({
17
+ port: 3088,
18
+ https: false,
19
+ handlers: {
20
+ "/s/:type": (req, res) => {
21
+ res.setHeader("Content-Type", "text/javascript");
22
+ res.send({ ok: true });
23
+ requestLog.push({
24
+ type: req.params.type,
25
+ body: req.body,
26
+ });
27
+ },
28
+ },
29
+ });
30
+ console.log("Running on " + server.baseUrl);
31
+ };
32
+ const shutdownServer = async () => {
33
+ console.log("Shutting down server " + server.baseUrl);
34
+ await server.close();
35
+ };
36
+
37
+ test("node js", async () => {
38
+ await startServer();
39
+ try {
40
+ const jitsu: AnalyticsInterface = jitsuAnalytics({
41
+ writeKey: "test",
42
+ host: server.baseUrl,
43
+ debug: true,
44
+ fetch: fetchImpl,
45
+ });
46
+ await jitsu.track("testTrack");
47
+
48
+ await jitsu.identify("testUser", {
49
+ email: "test@test.com",
50
+ });
51
+
52
+ await jitsu.page({
53
+ name: "test",
54
+ environment: "nodejs",
55
+ context: {
56
+ page: {
57
+ url: "http://server.com",
58
+ },
59
+ },
60
+ });
61
+ await new Promise(resolve => setTimeout(resolve, 1000));
62
+ expect(requestLog.length).toBe(3);
63
+ expect(requestLog[0].type).toBe("track");
64
+ expect(requestLog[1].type).toBe("identify");
65
+ expect(requestLog[2].type).toBe("page");
66
+
67
+ const track = requestLog[0].body as AnalyticsClientEvent;
68
+ const identify = requestLog[1].body as AnalyticsClientEvent;
69
+ const page = requestLog[2].body as AnalyticsClientEvent;
70
+
71
+ //expect(track.userId).toBe(undefined);
72
+ expect(page.properties.name).toBe("test");
73
+ expect(page.properties?.environment).toBe("nodejs");
74
+ expect(page.context.page.url).toBe("http://server.com");
75
+ expect(page.userId).toBe("testUser");
76
+ expect(identify.traits.email).toBe("test@test.com");
77
+ expect(identify.anonymousId).toBe(page.anonymousId);
78
+ const pagePayload = requestLog[0].body;
79
+ console.log("pagePayload", pagePayload);
80
+ } finally {
81
+ await shutdownServer();
82
+ }
83
+ });
84
+ });
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+
8
+ <title>Tracking page</title>
9
+ <script>
10
+ window.testOnload = async j => {
11
+ j.identify("user1", { email: "john.doe@gmail.com" });
12
+ j.track("pageLoaded", { trackParam: "trackValue" });
13
+ };
14
+ </script>
15
+ <script
16
+ type="text/javascript"
17
+ src="<%=trackingBase%>/p.js"
18
+ data-onload="testOnload"
19
+ data-debug="true"
20
+ defer
21
+ ></script>
22
+ </head>
23
+
24
+ <body>
25
+ <h1>Test</h1>
26
+ </body>
27
+ </html>
@@ -0,0 +1,64 @@
1
+ <html>
2
+ <head>
3
+ <script type="text/javascript">
4
+ !(function () {
5
+ var analytics = (window.analytics = window.analytics || []);
6
+ if (!analytics.initialize)
7
+ if (analytics.invoked) window.console && console.error && console.error("Segment snippet included twice.");
8
+ else {
9
+ analytics.invoked = !0;
10
+ analytics.methods = [
11
+ "trackSubmit",
12
+ "trackClick",
13
+ "trackLink",
14
+ "trackForm",
15
+ "pageview",
16
+ "identify",
17
+ "reset",
18
+ "group",
19
+ "track",
20
+ "ready",
21
+ "alias",
22
+ "debug",
23
+ "page",
24
+ "once",
25
+ "off",
26
+ "on",
27
+ "addSourceMiddleware",
28
+ "addIntegrationMiddleware",
29
+ "setAnonymousId",
30
+ "addDestinationMiddleware",
31
+ ];
32
+ analytics.factory = function (e) {
33
+ return function () {
34
+ var t = Array.prototype.slice.call(arguments);
35
+ t.unshift(e);
36
+ analytics.push(t);
37
+ return analytics;
38
+ };
39
+ };
40
+ for (var e = 0; e < analytics.methods.length; e++) {
41
+ var key = analytics.methods[e];
42
+ analytics[key] = analytics.factory(key);
43
+ }
44
+ analytics.load = function (key, e) {
45
+ var t = document.createElement("script");
46
+ t.type = "text/javascript";
47
+ t.async = !0;
48
+ t.src = "https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";
49
+ var n = document.getElementsByTagName("script")[0];
50
+ n.parentNode.insertBefore(t, n);
51
+ analytics._loadOptions = e;
52
+ };
53
+ analytics._writeKey = "<%= process.env.SEGMENT_WRITE_KEY%>";
54
+ analytics.SNIPPET_VERSION = "4.15.3";
55
+ analytics.load("<%= process.env.SEGMENT_WRITE_KEY%>");
56
+
57
+ analytics.ready(() => {
58
+ window.__analyticsReady = true;
59
+ });
60
+ }
61
+ })();
62
+ </script>
63
+ </head>
64
+ </html>
@@ -0,0 +1,262 @@
1
+ import { BrowserContext, expect, Page, test } from "@playwright/test";
2
+ import { createServer, SimpleSyrup } from "../simple-syrup";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import ejs from "ejs";
6
+ import chalk from "chalk";
7
+ import * as process from "process";
8
+ import * as console from "console";
9
+ import { AnalyticsClientEvent, AnalyticsInterface } from "@jitsu/types/analytics.d";
10
+ import type { AnalyticsCore } from "@segment/analytics-next/src/core/analytics/interfaces";
11
+ import { response } from "express";
12
+
13
+ test.use({
14
+ ignoreHTTPSErrors: true,
15
+ });
16
+
17
+ const express = require("express");
18
+ const cookieParser = require("cookie-parser");
19
+ const app = express();
20
+ //const forge = require("node-forge");
21
+
22
+ let server: SimpleSyrup;
23
+
24
+ let requestLog: { type: string; body: AnalyticsClientEvent }[] = [];
25
+
26
+ test.beforeAll(async () => {
27
+ const testCasesHandlers = fs.readdirSync(path.join(__dirname, "cases")).reduce((res, file) => {
28
+ console.log("Processing file", file);
29
+ return {
30
+ ...res,
31
+ [`/${file}`]: (req, res) => {
32
+ res.setHeader("Content-Type", "text/html");
33
+ res.send(
34
+ ejs.compile(
35
+ fs.readFileSync(path.join(__dirname, "cases", file)).toString(),
36
+ {}
37
+ )({
38
+ trackingBase: server.baseUrl,
39
+ })
40
+ );
41
+ },
42
+ };
43
+ }, {});
44
+ server = await createServer({
45
+ port: 3088,
46
+ https: process.env.DISABLE_HTTPS !== "1" && process.env.DISABLE_HTTPS !== "true",
47
+ handlers: {
48
+ "/p.js": (req, res) => {
49
+ res.setHeader("Content-Type", "text/javascript");
50
+ res.send(fs.readFileSync(path.join(__dirname, "../../dist/web/p.js.txt")).toString());
51
+ },
52
+ "/s/:type": (req, res) => {
53
+ res.setHeader("Content-Type", "text/javascript");
54
+ res.send({ ok: true });
55
+ requestLog.push({
56
+ type: req.params.type,
57
+ body: req.body,
58
+ });
59
+ },
60
+ ...testCasesHandlers,
61
+ },
62
+ });
63
+ console.log("Running on " + server.baseUrl);
64
+ });
65
+
66
+ test.afterAll(async () => {
67
+ await server?.close();
68
+ });
69
+
70
+ function sortKeysRecursively(obj: any): any {
71
+ if (typeof obj === "object" && obj !== null && !Array.isArray(obj)) {
72
+ return Object.keys(obj)
73
+ .sort()
74
+ .reduce((res, key) => {
75
+ res[key] = sortKeysRecursively(obj[key]);
76
+ return res;
77
+ }, {});
78
+ }
79
+ return obj;
80
+ }
81
+
82
+ function logColor(type: string) {
83
+ return (
84
+ {
85
+ error: chalk.bgRed,
86
+ warn: chalk.bgYellow,
87
+ info: chalk.bgBlack,
88
+ log: chalk.bgGray,
89
+ }[type] || (x => x)
90
+ );
91
+ }
92
+
93
+ function shouldKeepBrowserOpen() {
94
+ return process.env.KEEP_BROWSER_OPEN === "true" || process.env.KEEP_BROWSER_OPEN === "1";
95
+ }
96
+
97
+ async function createLoggingPage(browserContext: BrowserContext): Promise<{ page: Page; uncaughtErrors: Error[] }> {
98
+ const page = await browserContext.newPage();
99
+ const errors: Error[] = [];
100
+
101
+ page.on("pageerror", error => {
102
+ errors.push(error);
103
+ const border = chalk.cyan("│");
104
+ console.log();
105
+ console.log(`${border} ${chalk.cyan(`Browser Console UNCAUGHT ERROR:`)}`);
106
+ console.log(`${border} ` + error.stack.split("\n").join(`\n${border} `));
107
+ });
108
+
109
+ page.on("console", msg => {
110
+ const border = chalk.cyan("│");
111
+ console.log();
112
+ console.log(`${border} ${chalk.cyan(`Browser Console ${msg.type().toUpperCase()}`)}`);
113
+ console.log(`${border} ` + msg.text().split("\n").join(`\n${border} `));
114
+ });
115
+ return { page, uncaughtErrors: errors };
116
+ }
117
+
118
+ const generateTestEvents = async () => {
119
+ const implName = `${window["analytics"] ? "segment" : "jitsu"}`;
120
+ const analytics = (window["analytics"] || window["jitsu"]) as AnalyticsInterface;
121
+ console.log(`Generating test events. Implementation ${implName}: ${Object.keys(analytics)}`);
122
+ await analytics.identify("userId2", { email: "john.doe2@gmail.com", caseName: "basic-identify" });
123
+ // jitsu must extract traits even from 'id' object
124
+ await analytics.identify({ email: "john.doe3@gmail.com", caseName: "identify-without-user-id" });
125
+ await analytics.page({ caseName: "page-without-name", context: { page: { title: "Synthetic Title" } } });
126
+ await analytics.page("test-page", { caseName: "page-with-name" });
127
+ await analytics.track("testEvent", { caseName: "track-with-name" });
128
+ console.log(`Test events for ${implName} has been generated`);
129
+ };
130
+
131
+ /**
132
+ * This test isn't really testing anything. It generates reference segment events
133
+ */
134
+ test("segment-reference", async ({ browser }) => {
135
+ if (!process.env.SEGMENT_WRITE_KEY) {
136
+ console.log("Skipping segment reference generation, no SEGMENT_WRITE_KEY provided");
137
+ return;
138
+ }
139
+ // Using the browser fixture, you can get access to the BrowserContext
140
+ const browserContext = await browser.newContext();
141
+ const { page } = await createLoggingPage(browserContext);
142
+ const requests: Record<string, { payload: any }[]> = {};
143
+ page.on("response", async response => {
144
+ const request = response.request();
145
+ const apiPrefix = "https://api.segment.io/v1/";
146
+ if (request.url().startsWith(apiPrefix) && request.method() === "POST") {
147
+ const type = request.url().substring(apiPrefix.length);
148
+ requests[type] = requests[type] || [];
149
+ requests[type].push({
150
+ payload: await request.postDataJSON(),
151
+ });
152
+ }
153
+ console.log(`Request ${request.method()} ${request.url()} → ${response.status()}`);
154
+ });
155
+
156
+ await page.goto(`${server.baseUrl}/segment-reference.html?utm_source=source&utm_medium=medium&utm_campaign=campaign`);
157
+
158
+ await page.waitForFunction(() => window["__analyticsReady"] === true, undefined, {
159
+ timeout: 5000,
160
+ polling: 100,
161
+ });
162
+ console.log("Segment has been page loaded. Sending events");
163
+ await page.evaluate(generateTestEvents);
164
+ const cookies = (await browserContext.cookies()).reduce(
165
+ (res, cookie) => ({
166
+ ...res,
167
+ [cookie.name]: cookie.value,
168
+ }),
169
+ {}
170
+ );
171
+ console.log("🍪 Segment Cookies", cookies);
172
+
173
+ for (const type of Object.keys(requests)) {
174
+ for (const { payload } of requests[type]) {
175
+ const dir = path.join(__dirname, "artifacts", "segment-reference");
176
+ fs.mkdirSync(dir, { recursive: true });
177
+ const file = path.join(
178
+ dir,
179
+ `${payload.traits?.caseName || payload.properties?.caseName || payload.context?.caseName}.json`
180
+ );
181
+ fs.writeFileSync(file, JSON.stringify(sortKeysRecursively(payload), null, 2));
182
+ }
183
+ }
184
+ });
185
+
186
+ test("basic", async ({ browser }) => {
187
+ const browserContext = await browser.newContext();
188
+
189
+ const { page: firstPage, uncaughtErrors: fistPageErrors } = await createLoggingPage(browserContext);
190
+ const [pageResult] = await Promise.all([
191
+ firstPage.goto(`${server.baseUrl}/basic.html?utm_source=source&utm_medium=medium&utm_campaign=campaign`),
192
+ ]);
193
+
194
+ await firstPage.waitForFunction(() => window["jitsu"] !== undefined, undefined, {
195
+ timeout: 1000,
196
+ polling: 100,
197
+ });
198
+ expect(pageResult.status()).toBe(200);
199
+ const cookies = (await browserContext.cookies()).reduce(
200
+ (res, cookie) => ({
201
+ ...res,
202
+ [cookie.name]: cookie.value,
203
+ }),
204
+ {}
205
+ );
206
+ console.log("🍪 Jitsu Cookies", cookies);
207
+ expect(fistPageErrors.length).toEqual(0);
208
+ const anonymousId = cookies["__eventn_id"];
209
+ expect(anonymousId).toBeDefined();
210
+ expect(cookies["__eventn_uid"]).toBe("user1");
211
+ expect(cookies["__eventn_id_usr"]).toBeDefined();
212
+ expect(JSON.parse(cookies["__eventn_id_usr"]).email).toEqual("john.doe@gmail.com");
213
+ let identifies = requestLog.filter(x => x.type === "identify");
214
+ let pages = requestLog.filter(x => x.type === "page");
215
+ let tracks = requestLog.filter(x => x.type === "track");
216
+ expect(identifies.length).toBe(1);
217
+ expect(pages.length).toBe(1);
218
+ expect(tracks.length).toBe(1);
219
+
220
+ const track = tracks[0].body as AnalyticsClientEvent;
221
+ const page = pages[0].body as AnalyticsClientEvent;
222
+ const identify = identifies[0].body as AnalyticsClientEvent;
223
+
224
+ console.log(chalk.bold("📝 Checking track event"), JSON.stringify(track, null, 3));
225
+ expect(track.properties.trackParam).toEqual("trackValue");
226
+ expect(track.type).toEqual("track");
227
+ expect(track.context.traits.email).toEqual("john.doe@gmail.com");
228
+ expect(track.userId).toEqual("user1");
229
+ expect(track.event).toEqual("pageLoaded");
230
+
231
+ console.log(chalk.bold("📝 Checking identify event"), JSON.stringify(identify, null, 3));
232
+ expect(identify.traits.email).toEqual("john.doe@gmail.com");
233
+ expect(identify.userId).toEqual("user1");
234
+ expect(identify.anonymousId).toEqual(anonymousId);
235
+
236
+ console.log(chalk.bold("📝 Checking page event"), JSON.stringify(page, null, 3));
237
+ expect(page.anonymousId).toEqual(anonymousId);
238
+ expect(page.context.traits.email).toEqual("john.doe@gmail.com");
239
+ expect(page.userId).toEqual("user1");
240
+
241
+ expect(page.context.campaign.source).toEqual("source");
242
+
243
+ const { page: secondPage, uncaughtErrors: secondPageErrors } = await createLoggingPage(browserContext);
244
+ await secondPage.goto(`${server.baseUrl}/basic.html?utm_source=source&utm_medium=medium&utm_campaign=campaign`);
245
+ await secondPage.waitForFunction(() => window["jitsu"] !== undefined, undefined, {
246
+ timeout: 1000,
247
+ polling: 100,
248
+ });
249
+ requestLog.length = 0;
250
+
251
+ await secondPage.evaluate(generateTestEvents);
252
+ expect(secondPageErrors.length).toBe(0);
253
+ requestLog.forEach(({ body: payload }) => {
254
+ const dir = path.join(__dirname, "artifacts", "requests");
255
+ fs.mkdirSync(dir, { recursive: true });
256
+ const file = path.join(
257
+ dir,
258
+ `${payload.traits?.caseName || payload.properties?.caseName || payload.context?.caseName}.json`
259
+ );
260
+ fs.writeFileSync(file, JSON.stringify(sortKeysRecursively(payload), null, 2));
261
+ });
262
+ });
@@ -0,0 +1,130 @@
1
+ import type { Application, RequestHandler } from "express";
2
+ import http from "http";
3
+ import https from "https";
4
+
5
+ const express = require("express");
6
+
7
+ const forge = require("node-forge");
8
+
9
+ export type SimpleSyrupOpts = {
10
+ port?: number;
11
+ https?: boolean;
12
+ handlers?: Record<string, RequestHandler>;
13
+ };
14
+
15
+ export type SimpleSyrup = {
16
+ app: Application;
17
+ server: http.Server;
18
+ port: number;
19
+ baseUrl: string;
20
+ close: () => Promise<void>;
21
+ };
22
+
23
+ function getNextAvailablePort(port: number) {
24
+ return new Promise<number>(resolve => {
25
+ const server = http.createServer();
26
+ server.on("error", () => {
27
+ resolve(getNextAvailablePort(port + 1));
28
+ });
29
+ server.on("listening", () => {
30
+ server.close(() => {
31
+ resolve(port);
32
+ });
33
+ });
34
+ server.listen(port);
35
+ });
36
+ }
37
+
38
+ function generateX509Certificate(altNames: { type: number; value: string }[]) {
39
+ const issuer = [
40
+ { name: "commonName", value: "localhost" },
41
+ { name: "organizationName", value: "ACME Corp" },
42
+ { name: "organizationalUnitName", value: "XYZ Department" },
43
+ ];
44
+ const certificateExtensions = [
45
+ { name: "basicConstraints", cA: true },
46
+ {
47
+ name: "keyUsage",
48
+ keyCertSign: true,
49
+ digitalSignature: true,
50
+ nonRepudiation: true,
51
+ keyEncipherment: true,
52
+ dataEncipherment: true,
53
+ },
54
+ {
55
+ name: "extKeyUsage",
56
+ serverAuth: true,
57
+ clientAuth: true,
58
+ codeSigning: true,
59
+ emailProtection: true,
60
+ timeStamping: true,
61
+ },
62
+ {
63
+ name: "nsCertType",
64
+ client: true,
65
+ server: true,
66
+ email: true,
67
+ objsign: true,
68
+ sslCA: true,
69
+ emailCA: true,
70
+ objCA: true,
71
+ },
72
+ { name: "subjectAltName", altNames },
73
+ { name: "subjectKeyIdentifier" },
74
+ ];
75
+ const keys = forge.pki.rsa.generateKeyPair(2048);
76
+ const cert = forge.pki.createCertificate();
77
+ cert.validity.notBefore = new Date();
78
+ cert.validity.notAfter = new Date();
79
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
80
+ cert.publicKey = keys.publicKey;
81
+ cert.setSubject(issuer);
82
+ cert.setIssuer(issuer);
83
+ cert.setExtensions(certificateExtensions);
84
+ cert.sign(keys.privateKey);
85
+ return {
86
+ key: forge.pki.privateKeyToPem(keys.privateKey),
87
+ cert: forge.pki.certificateToPem(cert),
88
+ };
89
+ }
90
+
91
+ function shutdownFunction(server): Promise<void> {
92
+ return new Promise<void>(resolve => {
93
+ server.close(() => {
94
+ resolve();
95
+ });
96
+ });
97
+ }
98
+
99
+ export function createServer(opts: SimpleSyrupOpts = {}): Promise<SimpleSyrup> {
100
+ return new Promise<SimpleSyrup>(async (resolve, reject) => {
101
+ const app: Application = express();
102
+ app.use(express.json());
103
+ const server = opts?.https ? https.createServer(generateX509Certificate([]), app) : http.createServer(app);
104
+ const port = opts?.port ? await getNextAvailablePort(opts.port) : 0;
105
+ server.listen(port, () => {
106
+ const address = server.address();
107
+ if (!address) {
108
+ reject(new Error(`Unable to get server address`));
109
+ return;
110
+ }
111
+ if (typeof address === "string") {
112
+ reject(new Error(`Address is not an of network. This is not supported: ${address} `));
113
+ return;
114
+ }
115
+ const port = address.port;
116
+ if (opts?.handlers) {
117
+ for (const [path, handler] of Object.entries(opts?.handlers)) {
118
+ app.all(path, handler);
119
+ }
120
+ }
121
+ resolve({
122
+ app: app,
123
+ port,
124
+ close: () => shutdownFunction(server),
125
+ baseUrl: `${opts.https ? "https" : "http"}://localhost:${port}`,
126
+ server: server,
127
+ });
128
+ });
129
+ });
130
+ }