@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.
- package/dist/next/index.d.mts +50 -0
- package/dist/next/index.d.ts +50 -0
- package/dist/next/index.js +97 -0
- package/dist/next/index.mjs +67 -0
- package/package.json +19 -1
|
@@ -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.
|
|
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",
|