@shipeasy/sdk 4.4.0 → 4.5.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,50 @@
1
+ import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
2
+
3
+ /** The first-party cookie that carries the stable anonymous bucketing unit. */
4
+ declare const ANON_ID_COOKIE = "__se_anon_id";
5
+ interface AnonIdResult {
6
+ /** The stable bucketing id for this request (existing cookie, or freshly minted). */
7
+ anonId: string;
8
+ /** True when there was no valid cookie and we minted a new id (must be persisted). */
9
+ minted: boolean;
10
+ }
11
+ /**
12
+ * Read + validate the `__se_anon_id` cookie, minting one when absent/invalid.
13
+ * When `requestHeaders` is supplied and we mint, the new id is appended to the
14
+ * forwarded `cookie` header so THIS request's SSR (and any inner middleware)
15
+ * already sees it. Pair with {@link commitAnonId} to persist a minted id.
16
+ */
17
+ declare function readOrMintAnonId(req: NextRequest, requestHeaders?: Headers): AnonIdResult;
18
+ /**
19
+ * Persist a freshly-minted anon id as a first-party cookie on `res` (no-op when
20
+ * `minted` is false). Non-httpOnly by contract — the browser SDK reads it via
21
+ * `document.cookie` to bucket identically to SSR. Returns `res` for chaining.
22
+ */
23
+ declare function commitAnonId(res: NextResponse, result: AnonIdResult, req: NextRequest): NextResponse;
24
+ type ShipeasyMiddleware = (req: NextRequest, event: NextFetchEvent) => NextResponse | Response | undefined | null | void | Promise<NextResponse | Response | undefined | null | void>;
25
+ /**
26
+ * Wrap an existing Next middleware so every matched request also mints the
27
+ * shared `__se_anon_id` cookie. Your middleware runs against a request that
28
+ * already carries the id (its own logic + SSR see it), and the cookie is set on
29
+ * whatever response it returns. Called with no argument, returns a standalone
30
+ * mint-only middleware (what the default {@link middleware} export uses).
31
+ *
32
+ * If your middleware forwards custom request headers via
33
+ * `NextResponse.next({ request: { headers } })`, prefer composing the
34
+ * {@link readOrMintAnonId} + {@link commitAnonId} primitives inside it — that
35
+ * preserves your forwarding verbatim; this wrapper rebuilds the pass-through
36
+ * `next()` and so would drop request-header forwarding the inner handler added.
37
+ */
38
+ declare function withShipeasy(handler?: ShipeasyMiddleware): ShipeasyMiddleware;
39
+ /** Drop-in middleware: `export { middleware, config } from "@shipeasy/sdk/next";` */
40
+ declare const middleware: ShipeasyMiddleware;
41
+ /**
42
+ * Matcher for the drop-in middleware: every HTML route (landing + app), skipping
43
+ * Next internals, API routes, and static files (any path segment with a dot).
44
+ * Define your own `config` if you need a narrower scope.
45
+ */
46
+ declare const config: {
47
+ matcher: string[];
48
+ };
49
+
50
+ export { ANON_ID_COOKIE, type AnonIdResult, type ShipeasyMiddleware, commitAnonId, config, middleware, readOrMintAnonId, withShipeasy };
@@ -0,0 +1,50 @@
1
+ import { NextRequest, NextFetchEvent, NextResponse } from 'next/server';
2
+
3
+ /** The first-party cookie that carries the stable anonymous bucketing unit. */
4
+ declare const ANON_ID_COOKIE = "__se_anon_id";
5
+ interface AnonIdResult {
6
+ /** The stable bucketing id for this request (existing cookie, or freshly minted). */
7
+ anonId: string;
8
+ /** True when there was no valid cookie and we minted a new id (must be persisted). */
9
+ minted: boolean;
10
+ }
11
+ /**
12
+ * Read + validate the `__se_anon_id` cookie, minting one when absent/invalid.
13
+ * When `requestHeaders` is supplied and we mint, the new id is appended to the
14
+ * forwarded `cookie` header so THIS request's SSR (and any inner middleware)
15
+ * already sees it. Pair with {@link commitAnonId} to persist a minted id.
16
+ */
17
+ declare function readOrMintAnonId(req: NextRequest, requestHeaders?: Headers): AnonIdResult;
18
+ /**
19
+ * Persist a freshly-minted anon id as a first-party cookie on `res` (no-op when
20
+ * `minted` is false). Non-httpOnly by contract — the browser SDK reads it via
21
+ * `document.cookie` to bucket identically to SSR. Returns `res` for chaining.
22
+ */
23
+ declare function commitAnonId(res: NextResponse, result: AnonIdResult, req: NextRequest): NextResponse;
24
+ type ShipeasyMiddleware = (req: NextRequest, event: NextFetchEvent) => NextResponse | Response | undefined | null | void | Promise<NextResponse | Response | undefined | null | void>;
25
+ /**
26
+ * Wrap an existing Next middleware so every matched request also mints the
27
+ * shared `__se_anon_id` cookie. Your middleware runs against a request that
28
+ * already carries the id (its own logic + SSR see it), and the cookie is set on
29
+ * whatever response it returns. Called with no argument, returns a standalone
30
+ * mint-only middleware (what the default {@link middleware} export uses).
31
+ *
32
+ * If your middleware forwards custom request headers via
33
+ * `NextResponse.next({ request: { headers } })`, prefer composing the
34
+ * {@link readOrMintAnonId} + {@link commitAnonId} primitives inside it — that
35
+ * preserves your forwarding verbatim; this wrapper rebuilds the pass-through
36
+ * `next()` and so would drop request-header forwarding the inner handler added.
37
+ */
38
+ declare function withShipeasy(handler?: ShipeasyMiddleware): ShipeasyMiddleware;
39
+ /** Drop-in middleware: `export { middleware, config } from "@shipeasy/sdk/next";` */
40
+ declare const middleware: ShipeasyMiddleware;
41
+ /**
42
+ * Matcher for the drop-in middleware: every HTML route (landing + app), skipping
43
+ * Next internals, API routes, and static files (any path segment with a dot).
44
+ * Define your own `config` if you need a narrower scope.
45
+ */
46
+ declare const config: {
47
+ matcher: string[];
48
+ };
49
+
50
+ export { ANON_ID_COOKIE, type AnonIdResult, type ShipeasyMiddleware, commitAnonId, config, middleware, readOrMintAnonId, withShipeasy };
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/next/index.ts
21
+ var next_exports = {};
22
+ __export(next_exports, {
23
+ ANON_ID_COOKIE: () => ANON_ID_COOKIE,
24
+ commitAnonId: () => commitAnonId,
25
+ config: () => config,
26
+ middleware: () => middleware,
27
+ readOrMintAnonId: () => readOrMintAnonId,
28
+ withShipeasy: () => withShipeasy
29
+ });
30
+ module.exports = __toCommonJS(next_exports);
31
+ var import_server = require("next/server");
32
+ var ANON_ID_COOKIE = "__se_anon_id";
33
+ var ANON_ID_RX = /^[A-Za-z0-9_-]{1,64}$/;
34
+ var ANON_ID_MAX_AGE = 60 * 60 * 24 * 365;
35
+ function mint() {
36
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
37
+ }
38
+ function appendCookie(headers, name, value) {
39
+ const existing = headers.get("cookie") ?? "";
40
+ const parts = existing.split(";").map((c) => c.trim()).filter((c) => c && !c.startsWith(`${name}=`));
41
+ parts.push(`${name}=${value}`);
42
+ headers.set("cookie", parts.join("; "));
43
+ }
44
+ function readOrMintAnonId(req, requestHeaders) {
45
+ const raw = req.cookies.get(ANON_ID_COOKIE)?.value;
46
+ const valid = !!raw && ANON_ID_RX.test(raw);
47
+ const anonId = valid ? raw : mint();
48
+ const minted = !valid;
49
+ if (minted && requestHeaders) appendCookie(requestHeaders, ANON_ID_COOKIE, anonId);
50
+ return { anonId, minted };
51
+ }
52
+ function commitAnonId(res, result, req) {
53
+ if (result.minted) {
54
+ res.cookies.set(ANON_ID_COOKIE, result.anonId, {
55
+ httpOnly: false,
56
+ secure: req.nextUrl.protocol === "https:",
57
+ sameSite: "lax",
58
+ path: "/",
59
+ maxAge: ANON_ID_MAX_AGE
60
+ });
61
+ }
62
+ return res;
63
+ }
64
+ function withShipeasy(handler) {
65
+ return async (req, event) => {
66
+ const requestHeaders = new Headers(req.headers);
67
+ const anon = readOrMintAnonId(req, requestHeaders);
68
+ if (anon.minted) req.cookies.set(ANON_ID_COOKIE, anon.anonId);
69
+ const userRes = handler ? await handler(req, event) : void 0;
70
+ const isPassThrough = !userRes || userRes instanceof import_server.NextResponse && userRes.headers.get("x-middleware-next") === "1";
71
+ if (isPassThrough) {
72
+ const res2 = import_server.NextResponse.next({ request: { headers: requestHeaders } });
73
+ if (userRes instanceof import_server.NextResponse) {
74
+ userRes.cookies.getAll().forEach((c) => res2.cookies.set(c));
75
+ userRes.headers.forEach((v, k) => {
76
+ if (k !== "x-middleware-next") res2.headers.set(k, v);
77
+ });
78
+ }
79
+ return commitAnonId(res2, anon, req);
80
+ }
81
+ const res = userRes instanceof import_server.NextResponse ? userRes : userRes instanceof Response ? new import_server.NextResponse(userRes.body, userRes) : import_server.NextResponse.next({ request: { headers: requestHeaders } });
82
+ return commitAnonId(res, anon, req);
83
+ };
84
+ }
85
+ var middleware = withShipeasy();
86
+ var config = {
87
+ matcher: ["/((?!api/|_next/|.*\\..*).*)"]
88
+ };
89
+ // Annotate the CommonJS export names for ESM import in node:
90
+ 0 && (module.exports = {
91
+ ANON_ID_COOKIE,
92
+ commitAnonId,
93
+ config,
94
+ middleware,
95
+ readOrMintAnonId,
96
+ withShipeasy
97
+ });
@@ -0,0 +1,67 @@
1
+ // src/next/index.ts
2
+ import { NextResponse } from "next/server";
3
+ var ANON_ID_COOKIE = "__se_anon_id";
4
+ var ANON_ID_RX = /^[A-Za-z0-9_-]{1,64}$/;
5
+ var ANON_ID_MAX_AGE = 60 * 60 * 24 * 365;
6
+ function mint() {
7
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `anon_${Math.random().toString(36).slice(2)}`;
8
+ }
9
+ function appendCookie(headers, name, value) {
10
+ const existing = headers.get("cookie") ?? "";
11
+ const parts = existing.split(";").map((c) => c.trim()).filter((c) => c && !c.startsWith(`${name}=`));
12
+ parts.push(`${name}=${value}`);
13
+ headers.set("cookie", parts.join("; "));
14
+ }
15
+ function readOrMintAnonId(req, requestHeaders) {
16
+ const raw = req.cookies.get(ANON_ID_COOKIE)?.value;
17
+ const valid = !!raw && ANON_ID_RX.test(raw);
18
+ const anonId = valid ? raw : mint();
19
+ const minted = !valid;
20
+ if (minted && requestHeaders) appendCookie(requestHeaders, ANON_ID_COOKIE, anonId);
21
+ return { anonId, minted };
22
+ }
23
+ function commitAnonId(res, result, req) {
24
+ if (result.minted) {
25
+ res.cookies.set(ANON_ID_COOKIE, result.anonId, {
26
+ httpOnly: false,
27
+ secure: req.nextUrl.protocol === "https:",
28
+ sameSite: "lax",
29
+ path: "/",
30
+ maxAge: ANON_ID_MAX_AGE
31
+ });
32
+ }
33
+ return res;
34
+ }
35
+ function withShipeasy(handler) {
36
+ return async (req, event) => {
37
+ const requestHeaders = new Headers(req.headers);
38
+ const anon = readOrMintAnonId(req, requestHeaders);
39
+ if (anon.minted) req.cookies.set(ANON_ID_COOKIE, anon.anonId);
40
+ const userRes = handler ? await handler(req, event) : void 0;
41
+ const isPassThrough = !userRes || userRes instanceof NextResponse && userRes.headers.get("x-middleware-next") === "1";
42
+ if (isPassThrough) {
43
+ const res2 = NextResponse.next({ request: { headers: requestHeaders } });
44
+ if (userRes instanceof NextResponse) {
45
+ userRes.cookies.getAll().forEach((c) => res2.cookies.set(c));
46
+ userRes.headers.forEach((v, k) => {
47
+ if (k !== "x-middleware-next") res2.headers.set(k, v);
48
+ });
49
+ }
50
+ return commitAnonId(res2, anon, req);
51
+ }
52
+ const res = userRes instanceof NextResponse ? userRes : userRes instanceof Response ? new NextResponse(userRes.body, userRes) : NextResponse.next({ request: { headers: requestHeaders } });
53
+ return commitAnonId(res, anon, req);
54
+ };
55
+ }
56
+ var middleware = withShipeasy();
57
+ var config = {
58
+ matcher: ["/((?!api/|_next/|.*\\..*).*)"]
59
+ };
60
+ export {
61
+ ANON_ID_COOKIE,
62
+ commitAnonId,
63
+ config,
64
+ middleware,
65
+ readOrMintAnonId,
66
+ withShipeasy
67
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://shipeasy.ai",
@@ -21,6 +21,9 @@
21
21
  ],
22
22
  "server": [
23
23
  "./dist/server/index.d.ts"
24
+ ],
25
+ "next": [
26
+ "./dist/next/index.d.ts"
24
27
  ]
25
28
  }
26
29
  },
@@ -39,11 +42,17 @@
39
42
  "types": "./dist/client/index.d.ts",
40
43
  "default": "./dist/client/index.js"
41
44
  },
45
+ "./next": {
46
+ "types": "./dist/next/index.d.ts",
47
+ "import": "./dist/next/index.mjs",
48
+ "default": "./dist/next/index.js"
49
+ },
42
50
  "./templates/*": "./templates/*.js"
43
51
  },
44
52
  "files": [
45
53
  "dist/server/",
46
54
  "dist/client/",
55
+ "dist/next/",
47
56
  "templates/",
48
57
  "LICENSE",
49
58
  "README.md"
@@ -58,9 +67,18 @@
58
67
  "dependencies": {
59
68
  "murmurhash-js": "^1.0.0"
60
69
  },
70
+ "peerDependencies": {
71
+ "next": ">=13"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "next": {
75
+ "optional": true
76
+ }
77
+ },
61
78
  "devDependencies": {
62
79
  "@types/murmurhash-js": "^1.0.6",
63
80
  "@types/node": "^20.0.0",
81
+ "next": "^16.2.3",
64
82
  "tsup": "^8.3.0",
65
83
  "typescript": "^5.7.4",
66
84
  "vitest": "^2.1.0",