@mohasinac/appkit 3.8.1 → 3.8.2

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/index.d.ts CHANGED
@@ -479,6 +479,7 @@ export { maskSecret } from "./security/index";
479
479
  export { rateLimit } from "./security/index";
480
480
  export { rateLimitByIdentifier } from "./security/index";
481
481
  export { redactPii, safeDisplayName, safeDisplayEmail } from "./security/index";
482
+ export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./security/index";
482
483
  export { requireActiveAccount } from "./security/index";
483
484
  export { requireAuth } from "./security/index";
484
485
  export { requireEmailVerified } from "./security/index";
package/dist/index.js CHANGED
@@ -885,6 +885,8 @@ export { rateLimitByIdentifier } from "./security/index";
885
885
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
886
886
  // redactPii - Shared export for redact pii.
887
887
  export { redactPii, safeDisplayName, safeDisplayEmail } from "./security/index";
888
+ // [CLIENT-SSR]-Runs in both SSR and browser â€" pure display-masking helpers, no crypto dependency.
889
+ export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./security/index";
888
890
  // [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
889
891
  // requireActiveAccount - Shared export for require active account.
890
892
  export { requireActiveAccount } from "./security/index";
@@ -4,7 +4,8 @@ export type { RateLimitConfig, RateLimitResult } from "./rate-limit";
4
4
  export { requireAuth, requireRole, requireOwnership, requireEmailVerified, requireActiveAccount, canChangeRole, getRoleLevel, } from "./authorization";
5
5
  export type { UserRole } from "./authorization";
6
6
  export { redactPii, safeDisplayName, safeDisplayEmail, maskIp } from "./pii-redact";
7
- export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPiiFields, encryptPii, decryptPii, isPiiEncrypted, piiBlindIndex, addPiiIndices, getPiiConfigError, encryptShippingAddress, decryptShippingAddress, encryptPayoutDetails, decryptPayoutDetails, encryptShippingConfig, decryptShippingConfig, encryptPayoutBankAccount, decryptPayoutBankAccount, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-encrypt";
7
+ export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPiiFields, encryptPii, decryptPii, piiBlindIndex, addPiiIndices, getPiiConfigError, encryptShippingAddress, decryptShippingAddress, encryptPayoutDetails, decryptPayoutDetails, encryptShippingConfig, decryptShippingConfig, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "./pii-encrypt";
8
+ export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-mask";
8
9
  export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, NEWSLETTER_PII_FIELDS, NEWSLETTER_PII_INDEX_MAP, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, CHAT_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
9
10
  export { encryptSecret, decryptSecret, isSecretEncrypted, maskSecret, } from "./settings-encryption";
10
11
  export type { Permission, RoleDefinition, RbacConfig, ResolvedUser, RbacHookReturn, } from "./rbac";
@@ -2,7 +2,11 @@ export { generateNonce, buildCSP } from "./csp";
2
2
  export { rateLimit, applyRateLimit, rateLimitByIdentifier, RateLimitPresets, clearRateLimitStore, } from "./rate-limit";
3
3
  export { requireAuth, requireRole, requireOwnership, requireEmailVerified, requireActiveAccount, canChangeRole, getRoleLevel, } from "./authorization";
4
4
  export { redactPii, safeDisplayName, safeDisplayEmail, maskIp } from "./pii-redact";
5
- export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPiiFields, encryptPii, decryptPii, isPiiEncrypted, piiBlindIndex, addPiiIndices, getPiiConfigError, encryptShippingAddress, decryptShippingAddress, encryptPayoutDetails, decryptPayoutDetails, encryptShippingConfig, decryptShippingConfig, encryptPayoutBankAccount, decryptPayoutBankAccount, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-encrypt";
5
+ export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPiiFields, encryptPii, decryptPii, piiBlindIndex, addPiiIndices, getPiiConfigError, encryptShippingAddress, decryptShippingAddress, encryptPayoutDetails, decryptPayoutDetails, encryptShippingConfig, decryptShippingConfig, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "./pii-encrypt";
6
+ // Crypto-free display-masking helpers -- sourced from pii-mask.ts (not
7
+ // pii-encrypt.ts) so this barrel stays safe to re-export from the
8
+ // universal client+server index.ts. See pii-mask.ts's header comment.
9
+ export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-mask";
6
10
  export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, NEWSLETTER_PII_FIELDS, NEWSLETTER_PII_INDEX_MAP, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, CHAT_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
7
11
  export { encryptSecret, decryptSecret, isSecretEncrypted, maskSecret, } from "./settings-encryption";
8
12
  export { DEFAULT_ROLES, resolvePermissions, hasPermission, hasAllPermissions, hasAnyPermission, createRbacHook, Can, createRequirePermission, createRequirePermissionSync, createRbacMiddleware, } from "./rbac";
@@ -9,8 +9,8 @@
9
9
  * Ciphertext format: "enc:v1:<iv_b64>:<ciphertext_b64>:<authtag_b64>"
10
10
  * Blind index format: "hmac-sha256:<sha256_hex>"
11
11
  */
12
- export declare const ENC_PREFIX = "enc:v1:";
13
- export declare const HMAC_PREFIX = "hmac-sha256:";
12
+ import { ENC_PREFIX, HMAC_PREFIX, isPiiEncrypted } from "./pii-mask";
13
+ export { ENC_PREFIX, HMAC_PREFIX, isPiiEncrypted };
14
14
  export declare function getPiiConfigError(): string | null;
15
15
  /** Encrypt a plaintext string with AES-256-GCM. */
16
16
  export declare function encryptValue(plaintext: string): string;
@@ -36,8 +36,6 @@ export declare function decryptPiiFields<T extends object>(doc: T, piiFields: st
36
36
  export declare function encryptPii(plaintext: string | null | undefined): string | null | undefined;
37
37
  /** Alias for decryptValue — decrypts a PII ciphertext. */
38
38
  export declare function decryptPii(ciphertext: string | null | undefined): string | null | undefined;
39
- /** Check if a value is encrypted by our PII system. */
40
- export declare function isPiiEncrypted(value: string): boolean;
41
39
  /** Alias for hmacBlindIndex. */
42
40
  export declare function piiBlindIndex(plaintext: string): string;
43
41
  /**
@@ -80,52 +78,3 @@ export declare function encryptPayoutBankAccount<T extends object>(bank: T | und
80
78
  * Decrypt PII on a payout bankAccount sub-object from the payouts collection.
81
79
  */
82
80
  export declare function decryptPayoutBankAccount<T extends object>(bank: T | undefined | null): T | undefined | null;
83
- /**
84
- * Mask a person's name for public display.
85
- * Each word is reduced to its first letter followed by "***".
86
- *
87
- * "John Doe" → "J*** D***"
88
- * "Alice" → "A***"
89
- *
90
- * If the value is still an encrypted blob (e.g. PII_ENCRYPTION_KEY not set),
91
- * returns "Anonymous" so encrypted ciphertext never leaks to the UI.
92
- */
93
- export declare function maskName(name: string | null | undefined): string;
94
- /**
95
- * Mask an email address for public display.
96
- * "john.doe@example.com" → "j***@***.com"
97
- */
98
- export declare function maskEmail(email: string | null | undefined): string;
99
- /**
100
- * Return a copy of a review document with PII fields masked for public display.
101
- * Admin/owner endpoints should NOT call this — use the raw document instead.
102
- */
103
- export declare function maskPublicReview<T extends {
104
- userName: string;
105
- }>(review: T): T;
106
- /**
107
- * Return a copy of a bid document with PII masked for public display
108
- * (auction product page).
109
- */
110
- export declare function maskPublicBid<T extends {
111
- userName: string;
112
- }>(bid: T): T;
113
- /**
114
- * Return a copy of an event-entry document with PII masked for the leaderboard.
115
- * ipAddress and userEmail are never sent to the client;
116
- * only userDisplayName needs masking for a public leaderboard view.
117
- */
118
- export declare function maskPublicEventEntry<T extends {
119
- userDisplayName?: string;
120
- userEmail?: string;
121
- ipAddress?: string;
122
- }>(entry: T): Omit<T, "userEmail" | "ipAddress">;
123
- /**
124
- * Return a copy of an offer document with buyer PII masked for the seller view.
125
- * The seller needs to know an offer was made and its amount, but the buyer's
126
- * full name and email should remain private until the order is confirmed.
127
- */
128
- export declare function maskOfferForSeller<T extends {
129
- buyerName: string;
130
- buyerEmail: string;
131
- }>(offer: T): T;
@@ -9,22 +9,26 @@
9
9
  * Ciphertext format: "enc:v1:<iv_b64>:<ciphertext_b64>:<authtag_b64>"
10
10
  * Blind index format: "hmac-sha256:<sha256_hex>"
11
11
  */
12
- import { createRequire } from "node:module";
13
- // crypto is a Node.js built-in. Use require() to keep it out of the static
14
- // import graph so Next.js Edge bundler does not warn about this file. A bare
15
- // top-level `require(...)` call only works when the compiled dist output is
16
- // loaded via CommonJS (or a bundler that polyfills `require`) — under a
17
- // plain Node ESM `import()` (e.g. appkit/scripts/seed-cli.mjs and any other
18
- // standalone script importing @mohasinac/appkit directly), there is no
19
- // ambient `require` global, so that call threw `ReferenceError: require is
20
- // not defined`. createRequire(import.meta.url) works in both: it's still a
21
- // runtime function call a bundler's static analysis won't follow into the
22
- // client graph, but it resolves correctly under Node's own ESM loader too.
23
- const nodeRequire = createRequire(import.meta.url);
24
- function nodeCrypto() { return nodeRequire("crypto"); }
12
+ import { ENC_PREFIX, HMAC_PREFIX, isPiiEncrypted } from "./pii-mask";
13
+ // Re-exported for backward-compat with existing direct imports of these
14
+ // symbols from this file the actual crypto-free definitions live in
15
+ // pii-mask.ts (see that file's header comment for why the split exists).
16
+ export { ENC_PREFIX, HMAC_PREFIX, isPiiEncrypted };
17
+ // crypto is a Node.js built-in. A bare, non-static `require(...)` call
18
+ // (never a top-level `import ... from "node:module"`/"crypto") is
19
+ // deliberate: bundlers targeting the browser (Turbopack in particular)
20
+ // statically resolve every top-level import in a module's chain before any
21
+ // tree-shaking pass, and hard-fail immediately on an unresolvable Node
22
+ // builtin even if the symbol using it is never actually consumed
23
+ // client-side. A bare `require` reference inside a function body is a
24
+ // runtime lookup, not a static import, so it's invisible to that pass.
25
+ // This only works where an ambient `require` global exists: Next.js's own
26
+ // server bundle (webpack/Turbopack) provides one automatically; the one
27
+ // place that doesn't is a standalone pure-ESM script importing
28
+ // @mohasinac/appkit directly (appkit/scripts/seed-cli.mjs), which sets
29
+ // `globalThis.require` itself before importing appkit — see that file.
30
+ function nodeCrypto() { return require("crypto"); }
25
31
  const ALGO = "aes-256-gcm";
26
- export const ENC_PREFIX = "enc:v1:";
27
- export const HMAC_PREFIX = "hmac-sha256:";
28
32
  function normalizePiiSecretValue(raw) {
29
33
  return (raw ?? "").replace(/(?:\\r|\\n|\r|\n)+$/g, "").trim();
30
34
  }
@@ -139,10 +143,6 @@ export function decryptPii(ciphertext) {
139
143
  return ciphertext;
140
144
  return decryptValue(ciphertext);
141
145
  }
142
- /** Check if a value is encrypted by our PII system. */
143
- export function isPiiEncrypted(value) {
144
- return typeof value === "string" && value.startsWith(ENC_PREFIX);
145
- }
146
146
  /** Alias for hmacBlindIndex. */
147
147
  export function piiBlindIndex(plaintext) {
148
148
  return hmacBlindIndex(plaintext);
@@ -278,84 +278,3 @@ export function decryptPayoutBankAccount(bank) {
278
278
  return bank;
279
279
  return decryptPiiFields({ ...bank }, []);
280
280
  }
281
- // --- Public display masking -------------------------------------------------
282
- /**
283
- * Mask a person's name for public display.
284
- * Each word is reduced to its first letter followed by "***".
285
- *
286
- * "John Doe" → "J*** D***"
287
- * "Alice" → "A***"
288
- *
289
- * If the value is still an encrypted blob (e.g. PII_ENCRYPTION_KEY not set),
290
- * returns "Anonymous" so encrypted ciphertext never leaks to the UI.
291
- */
292
- export function maskName(name) {
293
- if (!name || typeof name !== "string")
294
- return "Anonymous";
295
- if (isPiiEncrypted(name))
296
- return "Anonymous";
297
- return name
298
- .trim()
299
- .split(/\s+/)
300
- .filter(Boolean)
301
- .map((word) => word[0] + "***")
302
- .join(" ");
303
- }
304
- /**
305
- * Mask an email address for public display.
306
- * "john.doe@example.com" → "j***@***.com"
307
- */
308
- export function maskEmail(email) {
309
- if (!email || typeof email !== "string")
310
- return "***@***.***";
311
- if (isPiiEncrypted(email))
312
- return "***@***.***";
313
- const atIdx = email.indexOf("@");
314
- if (atIdx < 0)
315
- return "***";
316
- const local = email.slice(0, atIdx);
317
- const domain = email.slice(atIdx + 1);
318
- const dotIdx = domain.lastIndexOf(".");
319
- const domainMasked = dotIdx > 0 ? "***" + domain.slice(dotIdx) : "***";
320
- return (local[0] ?? "*") + "***@" + domainMasked;
321
- }
322
- /**
323
- * Return a copy of a review document with PII fields masked for public display.
324
- * Admin/owner endpoints should NOT call this — use the raw document instead.
325
- */
326
- export function maskPublicReview(review) {
327
- return { ...review, userName: maskName(review.userName) };
328
- }
329
- /**
330
- * Return a copy of a bid document with PII masked for public display
331
- * (auction product page).
332
- */
333
- export function maskPublicBid(bid) {
334
- return { ...bid };
335
- }
336
- /**
337
- * Return a copy of an event-entry document with PII masked for the leaderboard.
338
- * ipAddress and userEmail are never sent to the client;
339
- * only userDisplayName needs masking for a public leaderboard view.
340
- */
341
- export function maskPublicEventEntry(entry) {
342
- const { userEmail: _e, ipAddress: _ip, ...rest } = entry;
343
- return {
344
- ...rest,
345
- ...(rest.userDisplayName !== undefined
346
- ? { userDisplayName: maskName(rest.userDisplayName) }
347
- : {}),
348
- };
349
- }
350
- /**
351
- * Return a copy of an offer document with buyer PII masked for the seller view.
352
- * The seller needs to know an offer was made and its amount, but the buyer's
353
- * full name and email should remain private until the order is confirmed.
354
- */
355
- export function maskOfferForSeller(offer) {
356
- return {
357
- ...offer,
358
- buyerName: maskName(offer.buyerName),
359
- buyerEmail: maskEmail(offer.buyerEmail),
360
- };
361
- }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * PII display-masking helpers — pure string manipulation, zero runtime
3
+ * dependencies. Split out from pii-encrypt.ts so these can be exported
4
+ * from the universal client+server barrel (index.ts): pii-encrypt.ts has
5
+ * a top-level `import { createRequire } from "node:module"` (needed for
6
+ * the AES-256-GCM/HMAC functions to work under both Next.js's bundler and
7
+ * a pure-ESM standalone script context) that poisons the whole module for
8
+ * client bundling — Turbopack statically resolves every export in a file
9
+ * before tree-shaking, so re-exporting even one crypto-free symbol from
10
+ * pii-encrypt.ts still pulls `node:module` into the client graph and hard
11
+ * fails the browser build. This file has no such import and is safe
12
+ * anywhere `ReviewModal`/`ReviewDetailShell`/`ReviewsList` (client
13
+ * components displaying masked reviewer names) can reach it.
14
+ */
15
+ export declare const ENC_PREFIX = "enc:v1:";
16
+ export declare const HMAC_PREFIX = "hmac-sha256:";
17
+ /** Check if a value is encrypted by our PII system. */
18
+ export declare function isPiiEncrypted(value: string): boolean;
19
+ /**
20
+ * Mask a person's name for public display.
21
+ * Each word is reduced to its first letter followed by "***".
22
+ *
23
+ * "John Doe" → "J*** D***"
24
+ * "Alice" → "A***"
25
+ *
26
+ * If the value is still an encrypted blob (e.g. PII_ENCRYPTION_KEY not set),
27
+ * returns "Anonymous" so encrypted ciphertext never leaks to the UI.
28
+ */
29
+ export declare function maskName(name: string | null | undefined): string;
30
+ /**
31
+ * Mask an email address for public display.
32
+ * "john.doe@example.com" → "j***@***.com"
33
+ */
34
+ export declare function maskEmail(email: string | null | undefined): string;
35
+ /**
36
+ * Return a copy of a review document with PII fields masked for public display.
37
+ * Admin/owner endpoints should NOT call this — use the raw document instead.
38
+ */
39
+ export declare function maskPublicReview<T extends {
40
+ userName: string;
41
+ }>(review: T): T;
42
+ /**
43
+ * Return a copy of a bid document with PII masked for public display
44
+ * (auction product page).
45
+ */
46
+ export declare function maskPublicBid<T extends {
47
+ userName: string;
48
+ }>(bid: T): T;
49
+ /**
50
+ * Return a copy of an event-entry document with PII masked for the leaderboard.
51
+ * ipAddress and userEmail are never sent to the client;
52
+ * only userDisplayName needs masking for a public leaderboard view.
53
+ */
54
+ export declare function maskPublicEventEntry<T extends {
55
+ userDisplayName?: string;
56
+ userEmail?: string;
57
+ ipAddress?: string;
58
+ }>(entry: T): Omit<T, "userEmail" | "ipAddress">;
59
+ /**
60
+ * Return a copy of an offer document with buyer PII masked for the seller view.
61
+ * The seller needs to know an offer was made and its amount, but the buyer's
62
+ * full name and email should remain private until the order is confirmed.
63
+ */
64
+ export declare function maskOfferForSeller<T extends {
65
+ buyerName: string;
66
+ buyerEmail: string;
67
+ }>(offer: T): T;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * PII display-masking helpers — pure string manipulation, zero runtime
3
+ * dependencies. Split out from pii-encrypt.ts so these can be exported
4
+ * from the universal client+server barrel (index.ts): pii-encrypt.ts has
5
+ * a top-level `import { createRequire } from "node:module"` (needed for
6
+ * the AES-256-GCM/HMAC functions to work under both Next.js's bundler and
7
+ * a pure-ESM standalone script context) that poisons the whole module for
8
+ * client bundling — Turbopack statically resolves every export in a file
9
+ * before tree-shaking, so re-exporting even one crypto-free symbol from
10
+ * pii-encrypt.ts still pulls `node:module` into the client graph and hard
11
+ * fails the browser build. This file has no such import and is safe
12
+ * anywhere `ReviewModal`/`ReviewDetailShell`/`ReviewsList` (client
13
+ * components displaying masked reviewer names) can reach it.
14
+ */
15
+ export const ENC_PREFIX = "enc:v1:";
16
+ export const HMAC_PREFIX = "hmac-sha256:";
17
+ /** Check if a value is encrypted by our PII system. */
18
+ export function isPiiEncrypted(value) {
19
+ return typeof value === "string" && value.startsWith(ENC_PREFIX);
20
+ }
21
+ /**
22
+ * Mask a person's name for public display.
23
+ * Each word is reduced to its first letter followed by "***".
24
+ *
25
+ * "John Doe" → "J*** D***"
26
+ * "Alice" → "A***"
27
+ *
28
+ * If the value is still an encrypted blob (e.g. PII_ENCRYPTION_KEY not set),
29
+ * returns "Anonymous" so encrypted ciphertext never leaks to the UI.
30
+ */
31
+ export function maskName(name) {
32
+ if (!name || typeof name !== "string")
33
+ return "Anonymous";
34
+ if (isPiiEncrypted(name))
35
+ return "Anonymous";
36
+ return name
37
+ .trim()
38
+ .split(/\s+/)
39
+ .filter(Boolean)
40
+ .map((word) => word[0] + "***")
41
+ .join(" ");
42
+ }
43
+ /**
44
+ * Mask an email address for public display.
45
+ * "john.doe@example.com" → "j***@***.com"
46
+ */
47
+ export function maskEmail(email) {
48
+ if (!email || typeof email !== "string")
49
+ return "***@***.***";
50
+ if (isPiiEncrypted(email))
51
+ return "***@***.***";
52
+ const atIdx = email.indexOf("@");
53
+ if (atIdx < 0)
54
+ return "***";
55
+ const local = email.slice(0, atIdx);
56
+ const domain = email.slice(atIdx + 1);
57
+ const dotIdx = domain.lastIndexOf(".");
58
+ const domainMasked = dotIdx > 0 ? "***" + domain.slice(dotIdx) : "***";
59
+ return (local[0] ?? "*") + "***@" + domainMasked;
60
+ }
61
+ /**
62
+ * Return a copy of a review document with PII fields masked for public display.
63
+ * Admin/owner endpoints should NOT call this — use the raw document instead.
64
+ */
65
+ export function maskPublicReview(review) {
66
+ return { ...review, userName: maskName(review.userName) };
67
+ }
68
+ /**
69
+ * Return a copy of a bid document with PII masked for public display
70
+ * (auction product page).
71
+ */
72
+ export function maskPublicBid(bid) {
73
+ return { ...bid };
74
+ }
75
+ /**
76
+ * Return a copy of an event-entry document with PII masked for the leaderboard.
77
+ * ipAddress and userEmail are never sent to the client;
78
+ * only userDisplayName needs masking for a public leaderboard view.
79
+ */
80
+ export function maskPublicEventEntry(entry) {
81
+ const { userEmail: _e, ipAddress: _ip, ...rest } = entry;
82
+ return {
83
+ ...rest,
84
+ ...(rest.userDisplayName !== undefined
85
+ ? { userDisplayName: maskName(rest.userDisplayName) }
86
+ : {}),
87
+ };
88
+ }
89
+ /**
90
+ * Return a copy of an offer document with buyer PII masked for the seller view.
91
+ * The seller needs to know an offer was made and its amount, but the buyer's
92
+ * full name and email should remain private until the order is confirmed.
93
+ */
94
+ export function maskOfferForSeller(offer) {
95
+ return {
96
+ ...offer,
97
+ buyerName: maskName(offer.buyerName),
98
+ buyerEmail: maskEmail(offer.buyerEmail),
99
+ };
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "3.8.1",
3
+ "version": "3.8.2",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -117,6 +117,15 @@ if (ACTION === "load" && !PII_KEY_PRESENT) {
117
117
  // firebase-admin init
118
118
  // ---------------------------------------------------------------------------
119
119
  const require = createRequire(import.meta.url);
120
+ // appkit's pii-encrypt.ts calls a bare `require("crypto")` at runtime (not a
121
+ // static import — deliberately, so bundlers like Turbopack never see a
122
+ // node:module dependency and refuse to build the client bundle). A bare
123
+ // `require` identifier resolves via the global scope in Node ESM, so it must
124
+ // exist as a global before the dynamic `import("@mohasinac/appkit")` below
125
+ // ever triggers that code path. Only this standalone-script environment
126
+ // needs it — Next.js's own server bundle (webpack/Turbopack) provides an
127
+ // ambient `require` automatically.
128
+ globalThis.require = require;
120
129
  const admin = require("firebase-admin");
121
130
 
122
131
  function parsePrivateKey(raw) {