@walkeros/server-transformer-fingerprint 4.7.0-next-1790245733116 → 4.7.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/dev.d.mts +4 -20
- package/dist/dev.d.ts +4 -20
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/walkerOS.json +158 -99
- package/package.json +5 -5
package/dist/dev.d.mts
CHANGED
|
@@ -14,26 +14,10 @@ declare const SettingsSchema: z.ZodObject<{
|
|
|
14
14
|
hourly: "hourly";
|
|
15
15
|
none: "none";
|
|
16
16
|
}>>;
|
|
17
|
-
ip: z.ZodOptional<z.ZodUnion<readonly [z.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}, z.core.$strip>]>, z.ZodLiteral<false>]>>;
|
|
22
|
-
userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
23
|
-
key: z.ZodOptional<z.ZodString>;
|
|
24
|
-
value: z.ZodOptional<z.ZodUnknown>;
|
|
25
|
-
fn: z.ZodOptional<z.ZodString>;
|
|
26
|
-
}, z.core.$strip>]>, z.ZodLiteral<false>]>>;
|
|
27
|
-
site: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
28
|
-
key: z.ZodOptional<z.ZodString>;
|
|
29
|
-
value: z.ZodOptional<z.ZodUnknown>;
|
|
30
|
-
fn: z.ZodOptional<z.ZodString>;
|
|
31
|
-
}, z.core.$strip>]>, z.ZodLiteral<false>]>>;
|
|
32
|
-
fields: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
33
|
-
key: z.ZodOptional<z.ZodString>;
|
|
34
|
-
value: z.ZodOptional<z.ZodUnknown>;
|
|
35
|
-
fn: z.ZodOptional<z.ZodString>;
|
|
36
|
-
}, z.core.$strip>]>>>;
|
|
17
|
+
ip: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodLiteral<false>]>>;
|
|
18
|
+
userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodLiteral<false>]>>;
|
|
19
|
+
site: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodLiteral<false>]>>;
|
|
20
|
+
fields: z.ZodOptional<z.ZodArray<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
|
|
37
21
|
output: z.ZodOptional<z.ZodString>;
|
|
38
22
|
length: z.ZodOptional<z.ZodNumber>;
|
|
39
23
|
}, z.core.$strip>;
|
package/dist/dev.d.ts
CHANGED
|
@@ -14,26 +14,10 @@ declare const SettingsSchema: z.ZodObject<{
|
|
|
14
14
|
hourly: "hourly";
|
|
15
15
|
none: "none";
|
|
16
16
|
}>>;
|
|
17
|
-
ip: z.ZodOptional<z.ZodUnion<readonly [z.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}, z.core.$strip>]>, z.ZodLiteral<false>]>>;
|
|
22
|
-
userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
23
|
-
key: z.ZodOptional<z.ZodString>;
|
|
24
|
-
value: z.ZodOptional<z.ZodUnknown>;
|
|
25
|
-
fn: z.ZodOptional<z.ZodString>;
|
|
26
|
-
}, z.core.$strip>]>, z.ZodLiteral<false>]>>;
|
|
27
|
-
site: z.ZodOptional<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
28
|
-
key: z.ZodOptional<z.ZodString>;
|
|
29
|
-
value: z.ZodOptional<z.ZodUnknown>;
|
|
30
|
-
fn: z.ZodOptional<z.ZodString>;
|
|
31
|
-
}, z.core.$strip>]>, z.ZodLiteral<false>]>>;
|
|
32
|
-
fields: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
33
|
-
key: z.ZodOptional<z.ZodString>;
|
|
34
|
-
value: z.ZodOptional<z.ZodUnknown>;
|
|
35
|
-
fn: z.ZodOptional<z.ZodString>;
|
|
36
|
-
}, z.core.$strip>]>>>;
|
|
17
|
+
ip: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodLiteral<false>]>>;
|
|
18
|
+
userAgent: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodLiteral<false>]>>;
|
|
19
|
+
site: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodLiteral<false>]>>;
|
|
20
|
+
fields: z.ZodOptional<z.ZodArray<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
|
|
37
21
|
output: z.ZodOptional<z.ZodString>;
|
|
38
22
|
length: z.ZodOptional<z.ZodNumber>;
|
|
39
23
|
}, z.core.$strip>;
|
package/dist/dev.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=Object.defineProperty,
|
|
1
|
+
"use strict";var e,t=Object.defineProperty,s=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,a=(e,s)=>{for(var i in s)t(e,i,{get:s[i],enumerable:!0})},r={};a(r,{examples:()=>c,hints:()=>y,schemas:()=>o}),module.exports=(e=r,((e,a,r,o)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let d of i(a))n.call(e,d)||d===r||t(e,d,{get:()=>a[d],enumerable:!(o=s(a,d))||o.enumerable});return e})(t({},"__esModule",{value:!0}),e));var o={};a(o,{SettingsSchema:()=>g,settings:()=>u});var d=require("@walkeros/core/dev"),l=require("@walkeros/core/dev"),h=l.schemas.MappingSchemas.ValueSchema,p=l.z.union([h,l.z.literal(!1).describe("Switch this input off")]),g=l.z.object({salt:l.z.string().optional().describe('Secret key for the hash (HMAC), e.g. "$env.FINGERPRINT_SALT". Without it the hash can be reversed to the IP; a warning is logged.'),rotate:l.z.enum(["daily","hourly","none"]).optional().describe('Rotation window in UTC: the same visitor gets a new hash each window. Default: "daily"'),ip:p.optional().describe('Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: "ingest.ip" when fields is not set'),userAgent:p.optional().describe('User agent, reduced to browser, major version and OS before hashing. Default: "ingest.userAgent" when fields is not set'),site:p.optional().describe('Site the visitor is on; a URL is reduced to its hostname. Default: "event.source.url" when fields is not set'),fields:l.z.array(h).optional().describe("Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly."),output:l.z.string().optional().describe('Dot-notation path where hash is stored on the event. Default: "user.hash"'),length:l.z.number().int().positive().optional().describe("Truncate hash to this length. Default: full 64-char SHA-256 hash")}).describe("Fingerprint transformer: a cookieless, privacy-friendly visitor hash"),u=(0,d.zodToSchema)(g),c={};a(c,{step:()=>f});var f={};a(f,{ipAnonymization:()=>w,missingFields:()=>v,serverFingerprint:()=>m});var m={title:"Server fingerprint",description:'Default server fingerprint: the anonymized ingest.ip, the reduced ingest.userAgent and the site, keyed with settings.salt and rotated daily. Config: { salt: "$env.FINGERPRINT_SALT", length: 16 }. Requires source config.ingest. The hash depends on the salt and the day.',in:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},user:{hash:"158f99cc06e33fd6"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}}}]]},v={public:!1,description:"Graceful handling when ingest is missing: each empty input logs one warning, and the hash is still generated.",in:{name:"session start",data:{id:"s3ss10n"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"session start",data:{id:"s3ss10n"},user:{hash:"e183220b699c10a8"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}}}]]},w={title:"IP anonymization",description:'The ip input anonymizes the IP before hashing (IPv4 /24, IPv6 /48), so visitors from the same 10.0.42.* subnet with the same browser, major version and OS share a hash within a day. No custom fn needed. Config: { salt: "$env.FINGERPRINT_SALT", length: 16 }',in:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},user:{hash:"44d9154b9a9b3792"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}}}]]},y={"ingest-prerequisite":{text:'The default inputs ip and userAgent read ingest.ip and ingest.userAgent, so the server source must have config.ingest configured. config.ingest MUST use the map operator with direct request field paths (no req. prefix); a bare object like { ip: "req.ip" } is silently inert and leaves ingest empty. An input that resolves empty logs one warning ("Fingerprint input ... resolved empty") and the hash is still generated, but less distinct. Behind a load balancer the request ip is the balancer; read the client from headers.x-forwarded-for instead.',code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",next:"fingerprint",config:{settings:{port:8080},ingest:{map:{ip:{key:"ip"},userAgent:{key:"headers.user-agent"}}}}}},transformers:{fingerprint:{package:"@walkeros/server-transformer-fingerprint",config:{settings:{salt:"$env.FINGERPRINT_SALT"}}}}},null,2)}]},"salt-and-rotation":{text:'The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: "none" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: "none", ip: "ingest.ip", userAgent: "ingest.userAgent", site: "event.source.url", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: "none" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default.'},"site-sources":{text:'site defaults to the hostname of event.source.url, which the walkerOS browser and dataLayer sources set on every event and transformer-ga4 sets from the page URL of the hit (dl). Older transformer-ga4 versions did not set it, so upgrading transformer-ga4 changes the hash of GA4-decoded events once: site now joins the hash. Events without source.url (server-side events, GA4 hits without dl) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: "headers.origin" }), or a static { value: "example.com" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt.'},"extra-fields":{text:"fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48)."}};//# sourceMappingURL=dev.js.map
|
package/dist/dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nconst FieldSchema = z.union([\n z.string().describe('Dot-notation path: \"ingest.ip\", \"event.data.userId\"'),\n z\n .object({\n key: z.string().optional().describe('Source property path'),\n value: z.unknown().optional().describe('Static value or fallback'),\n fn: z\n .string()\n .optional()\n .describe(\n '$code: function receiving { event, ingest } that returns the value. Runs only when no key is set.',\n ),\n })\n .describe('Mapping value config for computed fields'),\n]);\n\nconst InputSchema = z.union([\n FieldSchema,\n z.literal(false).describe('Switch this input off'),\n]);\n\n/**\n * Fingerprint transformer settings schema.\n *\n * Mirrors: types.ts FingerprintSettings\n */\nexport const SettingsSchema = z\n .object({\n salt: z\n .string()\n .optional()\n .describe(\n 'Secret key for the hash (HMAC), e.g. \"$env.FINGERPRINT_SALT\". Without it the hash can be reversed to the IP; a warning is logged.',\n ),\n rotate: z\n .enum(['daily', 'hourly', 'none'])\n .optional()\n .describe(\n 'Rotation window in UTC: the same visitor gets a new hash each window. Default: \"daily\"',\n ),\n ip: InputSchema.optional().describe(\n 'Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: \"ingest.ip\" when fields is not set',\n ),\n userAgent: InputSchema.optional().describe(\n 'User agent, reduced to browser, major version and OS before hashing. Default: \"ingest.userAgent\" when fields is not set',\n ),\n site: InputSchema.optional().describe(\n 'Site the visitor is on; a URL is reduced to its hostname. Default: \"event.source.url\" when fields is not set',\n ),\n fields: z\n .array(FieldSchema)\n .optional()\n .describe(\n 'Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly.',\n ),\n output: z\n .string()\n .optional()\n .describe(\n 'Dot-notation path where hash is stored on the event. Default: \"user.hash\"',\n ),\n length: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Truncate hash to this length. Default: full 64-char SHA-256 hash',\n ),\n })\n .describe(\n 'Fingerprint transformer: a cookieless, privacy-friendly visitor hash',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nexport const serverFingerprint: Flow.StepExample = {\n title: 'Server fingerprint',\n description:\n 'Default server fingerprint: the anonymized ingest.ip, the reduced ingest.userAgent and the site, keyed with settings.salt and rotated daily. ' +\n 'Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }. Requires source config.ingest. The hash depends on the salt and the day.',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n user: { hash: '158f99cc06e33fd6' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const missingFields: Flow.StepExample = {\n public: false,\n description:\n 'Graceful handling when ingest is missing: each empty input logs one warning, and the hash is still generated.',\n in: {\n name: 'session start',\n data: { id: 's3ss10n' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'session start',\n data: { id: 's3ss10n' },\n user: { hash: 'e183220b699c10a8' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const ipAnonymization: Flow.StepExample = {\n title: 'IP anonymization',\n description:\n 'The ip input anonymizes the IP before hashing (IPv4 /24, IPv6 /48), so visitors from the same 10.0.42.* subnet ' +\n 'with the same browser, major version and OS share a hash within a day. No custom fn needed. Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n user: { hash: '44d9154b9a9b3792' },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The default inputs ip and userAgent read ingest.ip and ingest.userAgent, so the server source must have config.ingest configured. config.ingest MUST use the map operator with direct request field paths (no req. prefix); a bare object like { ip: \"req.ip\" } is silently inert and leaves ingest empty. An input that resolves empty logs one warning (\"Fingerprint input ... resolved empty\") and the hash is still generated, but less distinct. Behind a load balancer the request ip is the balancer; read the client from headers.x-forwarded-for instead.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n next: 'fingerprint',\n config: {\n settings: { port: 8080 },\n ingest: {\n map: {\n ip: { key: 'ip' },\n userAgent: { key: 'headers.user-agent' },\n },\n },\n },\n },\n },\n transformers: {\n fingerprint: {\n package: '@walkeros/server-transformer-fingerprint',\n config: {\n settings: { salt: '$env.FINGERPRINT_SALT' },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'salt-and-rotation': {\n text: 'The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: \"none\" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: \"none\", ip: \"ingest.ip\", userAgent: \"ingest.userAgent\", site: \"event.source.url\", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: \"none\" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default.',\n },\n 'site-sources': {\n text: 'site defaults to the hostname of event.source.url, which the walkerOS browser source sets on every event. Events without it (GA4 hits decoded by transformer-ga4, server-side events) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: \"headers.origin\" }), or a static { value: \"example.com\" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt.',\n },\n 'extra-fields': {\n text: 'fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48).',\n },\n};\n"],"mappings":"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","names":["import_dev"]}
|
|
1
|
+
{"version":3,"sources":["../src/dev.ts","../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\nexport { hints } from './hints';\n","import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { schemas, z } from '@walkeros/core/dev';\n\n// A field or input is a Mapping.Value (a path, a constant, a value config\n// or a fallback list), resolved via getMappingValue with { event, ingest }.\nconst FieldSchema = schemas.MappingSchemas.ValueSchema;\n\nconst InputSchema = z.union([\n FieldSchema,\n z.literal(false).describe('Switch this input off'),\n]);\n\n/**\n * Fingerprint transformer settings schema.\n *\n * Mirrors: types.ts FingerprintSettings\n */\nexport const SettingsSchema = z\n .object({\n salt: z\n .string()\n .optional()\n .describe(\n 'Secret key for the hash (HMAC), e.g. \"$env.FINGERPRINT_SALT\". Without it the hash can be reversed to the IP; a warning is logged.',\n ),\n rotate: z\n .enum(['daily', 'hourly', 'none'])\n .optional()\n .describe(\n 'Rotation window in UTC: the same visitor gets a new hash each window. Default: \"daily\"',\n ),\n ip: InputSchema.optional().describe(\n 'Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: \"ingest.ip\" when fields is not set',\n ),\n userAgent: InputSchema.optional().describe(\n 'User agent, reduced to browser, major version and OS before hashing. Default: \"ingest.userAgent\" when fields is not set',\n ),\n site: InputSchema.optional().describe(\n 'Site the visitor is on; a URL is reduced to its hostname. Default: \"event.source.url\" when fields is not set',\n ),\n fields: z\n .array(FieldSchema)\n .optional()\n .describe(\n 'Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly.',\n ),\n output: z\n .string()\n .optional()\n .describe(\n 'Dot-notation path where hash is stored on the event. Default: \"user.hash\"',\n ),\n length: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Truncate hash to this length. Default: full 64-char SHA-256 hash',\n ),\n })\n .describe(\n 'Fingerprint transformer: a cookieless, privacy-friendly visitor hash',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nexport const serverFingerprint: Flow.StepExample = {\n title: 'Server fingerprint',\n description:\n 'Default server fingerprint: the anonymized ingest.ip, the reduced ingest.userAgent and the site, keyed with settings.salt and rotated daily. ' +\n 'Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }. Requires source config.ingest. The hash depends on the salt and the day.',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n user: { hash: '158f99cc06e33fd6' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const missingFields: Flow.StepExample = {\n public: false,\n description:\n 'Graceful handling when ingest is missing: each empty input logs one warning, and the hash is still generated.',\n in: {\n name: 'session start',\n data: { id: 's3ss10n' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'session start',\n data: { id: 's3ss10n' },\n user: { hash: 'e183220b699c10a8' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const ipAnonymization: Flow.StepExample = {\n title: 'IP anonymization',\n description:\n 'The ip input anonymizes the IP before hashing (IPv4 /24, IPv6 /48), so visitors from the same 10.0.42.* subnet ' +\n 'with the same browser, major version and OS share a hash within a day. No custom fn needed. Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n user: { hash: '44d9154b9a9b3792' },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The default inputs ip and userAgent read ingest.ip and ingest.userAgent, so the server source must have config.ingest configured. config.ingest MUST use the map operator with direct request field paths (no req. prefix); a bare object like { ip: \"req.ip\" } is silently inert and leaves ingest empty. An input that resolves empty logs one warning (\"Fingerprint input ... resolved empty\") and the hash is still generated, but less distinct. Behind a load balancer the request ip is the balancer; read the client from headers.x-forwarded-for instead.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n next: 'fingerprint',\n config: {\n settings: { port: 8080 },\n ingest: {\n map: {\n ip: { key: 'ip' },\n userAgent: { key: 'headers.user-agent' },\n },\n },\n },\n },\n },\n transformers: {\n fingerprint: {\n package: '@walkeros/server-transformer-fingerprint',\n config: {\n settings: { salt: '$env.FINGERPRINT_SALT' },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'salt-and-rotation': {\n text: 'The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: \"none\" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: \"none\", ip: \"ingest.ip\", userAgent: \"ingest.userAgent\", site: \"event.source.url\", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: \"none\" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default.',\n },\n 'site-sources': {\n text: 'site defaults to the hostname of event.source.url, which the walkerOS browser and dataLayer sources set on every event and transformer-ga4 sets from the page URL of the hit (dl). Older transformer-ga4 versions did not set it, so upgrading transformer-ga4 changes the hash of GA4-decoded events once: site now joins the hash. Events without source.url (server-side events, GA4 hits without dl) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: \"headers.origin\" }), or a static { value: \"example.com\" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt.',\n },\n 'extra-fields': {\n text: 'fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48).',\n },\n};\n"],"mappings":"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","names":["import_dev"]}
|
package/dist/dev.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.defineProperty,t=(t,
|
|
1
|
+
var e=Object.defineProperty,t=(t,s)=>{for(var i in s)e(t,i,{get:s[i],enumerable:!0})},s={};t(s,{SettingsSchema:()=>d,settings:()=>h});import{zodToSchema as i}from"@walkeros/core/dev";import{schemas as n,z as a}from"@walkeros/core/dev";var r=n.MappingSchemas.ValueSchema,o=a.union([r,a.literal(!1).describe("Switch this input off")]),d=a.object({salt:a.string().optional().describe('Secret key for the hash (HMAC), e.g. "$env.FINGERPRINT_SALT". Without it the hash can be reversed to the IP; a warning is logged.'),rotate:a.enum(["daily","hourly","none"]).optional().describe('Rotation window in UTC: the same visitor gets a new hash each window. Default: "daily"'),ip:o.optional().describe('Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: "ingest.ip" when fields is not set'),userAgent:o.optional().describe('User agent, reduced to browser, major version and OS before hashing. Default: "ingest.userAgent" when fields is not set'),site:o.optional().describe('Site the visitor is on; a URL is reduced to its hostname. Default: "event.source.url" when fields is not set'),fields:a.array(r).optional().describe("Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly."),output:a.string().optional().describe('Dot-notation path where hash is stored on the event. Default: "user.hash"'),length:a.number().int().positive().optional().describe("Truncate hash to this length. Default: full 64-char SHA-256 hash")}).describe("Fingerprint transformer: a cookieless, privacy-friendly visitor hash"),h=i(d),l={};t(l,{step:()=>p});var p={};t(p,{ipAnonymization:()=>c,missingFields:()=>u,serverFingerprint:()=>g});var g={title:"Server fingerprint",description:'Default server fingerprint: the anonymized ingest.ip, the reduced ingest.userAgent and the site, keyed with settings.salt and rotated daily. Config: { salt: "$env.FINGERPRINT_SALT", length: 16 }. Requires source config.ingest. The hash depends on the salt and the day.',in:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Getting Started",id:"/docs/getting-started"},user:{hash:"158f99cc06e33fd6"},id:"ev-1700000600",trigger:"load",entity:"page",action:"view",timestamp:1700000600,source:{type:"express",platform:"server"}}}]]},u={public:!1,description:"Graceful handling when ingest is missing: each empty input logs one warning, and the hash is still generated.",in:{name:"session start",data:{id:"s3ss10n"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"session start",data:{id:"s3ss10n"},user:{hash:"e183220b699c10a8"},id:"ev-1700000601",trigger:"load",entity:"session",action:"start",timestamp:1700000601,source:{type:"express",platform:"server"}}}]]},c={title:"IP anonymization",description:'The ip input anonymizes the IP before hashing (IPv4 /24, IPv6 /48), so visitors from the same 10.0.42.* subnet with the same browser, major version and OS share a hash within a day. No custom fn needed. Config: { salt: "$env.FINGERPRINT_SALT", length: 16 }',in:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}},out:[["return",{event:{name:"page view",data:{domain:"www.example.com",title:"Privacy Policy",id:"/privacy"},user:{hash:"44d9154b9a9b3792"},id:"ev-1700000602",trigger:"load",entity:"page",action:"view",timestamp:1700000602,source:{type:"express",platform:"server"}}}]]},f={"ingest-prerequisite":{text:'The default inputs ip and userAgent read ingest.ip and ingest.userAgent, so the server source must have config.ingest configured. config.ingest MUST use the map operator with direct request field paths (no req. prefix); a bare object like { ip: "req.ip" } is silently inert and leaves ingest empty. An input that resolves empty logs one warning ("Fingerprint input ... resolved empty") and the hash is still generated, but less distinct. Behind a load balancer the request ip is the balancer; read the client from headers.x-forwarded-for instead.',code:[{lang:"json",code:JSON.stringify({sources:{express:{package:"@walkeros/server-source-express",next:"fingerprint",config:{settings:{port:8080},ingest:{map:{ip:{key:"ip"},userAgent:{key:"headers.user-agent"}}}}}},transformers:{fingerprint:{package:"@walkeros/server-transformer-fingerprint",config:{settings:{salt:"$env.FINGERPRINT_SALT"}}}}},null,2)}]},"salt-and-rotation":{text:'The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: "none" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: "none", ip: "ingest.ip", userAgent: "ingest.userAgent", site: "event.source.url", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: "none" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default.'},"site-sources":{text:'site defaults to the hostname of event.source.url, which the walkerOS browser and dataLayer sources set on every event and transformer-ga4 sets from the page URL of the hit (dl). Older transformer-ga4 versions did not set it, so upgrading transformer-ga4 changes the hash of GA4-decoded events once: site now joins the hash. Events without source.url (server-side events, GA4 hits without dl) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: "headers.origin" }), or a static { value: "example.com" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt.'},"extra-fields":{text:"fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48)."}};export{l as examples,f as hints,s as schemas};//# sourceMappingURL=dev.mjs.map
|
package/dist/dev.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { z } from '@walkeros/core/dev';\n\nconst FieldSchema = z.union([\n z.string().describe('Dot-notation path: \"ingest.ip\", \"event.data.userId\"'),\n z\n .object({\n key: z.string().optional().describe('Source property path'),\n value: z.unknown().optional().describe('Static value or fallback'),\n fn: z\n .string()\n .optional()\n .describe(\n '$code: function receiving { event, ingest } that returns the value. Runs only when no key is set.',\n ),\n })\n .describe('Mapping value config for computed fields'),\n]);\n\nconst InputSchema = z.union([\n FieldSchema,\n z.literal(false).describe('Switch this input off'),\n]);\n\n/**\n * Fingerprint transformer settings schema.\n *\n * Mirrors: types.ts FingerprintSettings\n */\nexport const SettingsSchema = z\n .object({\n salt: z\n .string()\n .optional()\n .describe(\n 'Secret key for the hash (HMAC), e.g. \"$env.FINGERPRINT_SALT\". Without it the hash can be reversed to the IP; a warning is logged.',\n ),\n rotate: z\n .enum(['daily', 'hourly', 'none'])\n .optional()\n .describe(\n 'Rotation window in UTC: the same visitor gets a new hash each window. Default: \"daily\"',\n ),\n ip: InputSchema.optional().describe(\n 'Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: \"ingest.ip\" when fields is not set',\n ),\n userAgent: InputSchema.optional().describe(\n 'User agent, reduced to browser, major version and OS before hashing. Default: \"ingest.userAgent\" when fields is not set',\n ),\n site: InputSchema.optional().describe(\n 'Site the visitor is on; a URL is reduced to its hostname. Default: \"event.source.url\" when fields is not set',\n ),\n fields: z\n .array(FieldSchema)\n .optional()\n .describe(\n 'Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly.',\n ),\n output: z\n .string()\n .optional()\n .describe(\n 'Dot-notation path where hash is stored on the event. Default: \"user.hash\"',\n ),\n length: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Truncate hash to this length. Default: full 64-char SHA-256 hash',\n ),\n })\n .describe(\n 'Fingerprint transformer: a cookieless, privacy-friendly visitor hash',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nexport const serverFingerprint: Flow.StepExample = {\n title: 'Server fingerprint',\n description:\n 'Default server fingerprint: the anonymized ingest.ip, the reduced ingest.userAgent and the site, keyed with settings.salt and rotated daily. ' +\n 'Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }. Requires source config.ingest. The hash depends on the salt and the day.',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n user: { hash: '158f99cc06e33fd6' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const missingFields: Flow.StepExample = {\n public: false,\n description:\n 'Graceful handling when ingest is missing: each empty input logs one warning, and the hash is still generated.',\n in: {\n name: 'session start',\n data: { id: 's3ss10n' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'session start',\n data: { id: 's3ss10n' },\n user: { hash: 'e183220b699c10a8' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const ipAnonymization: Flow.StepExample = {\n title: 'IP anonymization',\n description:\n 'The ip input anonymizes the IP before hashing (IPv4 /24, IPv6 /48), so visitors from the same 10.0.42.* subnet ' +\n 'with the same browser, major version and OS share a hash within a day. No custom fn needed. Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n user: { hash: '44d9154b9a9b3792' },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The default inputs ip and userAgent read ingest.ip and ingest.userAgent, so the server source must have config.ingest configured. config.ingest MUST use the map operator with direct request field paths (no req. prefix); a bare object like { ip: \"req.ip\" } is silently inert and leaves ingest empty. An input that resolves empty logs one warning (\"Fingerprint input ... resolved empty\") and the hash is still generated, but less distinct. Behind a load balancer the request ip is the balancer; read the client from headers.x-forwarded-for instead.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n next: 'fingerprint',\n config: {\n settings: { port: 8080 },\n ingest: {\n map: {\n ip: { key: 'ip' },\n userAgent: { key: 'headers.user-agent' },\n },\n },\n },\n },\n },\n transformers: {\n fingerprint: {\n package: '@walkeros/server-transformer-fingerprint',\n config: {\n settings: { salt: '$env.FINGERPRINT_SALT' },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'salt-and-rotation': {\n text: 'The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: \"none\" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: \"none\", ip: \"ingest.ip\", userAgent: \"ingest.userAgent\", site: \"event.source.url\", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: \"none\" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default.',\n },\n 'site-sources': {\n text: 'site defaults to the hostname of event.source.url, which the walkerOS browser source sets on every event. Events without it (GA4 hits decoded by transformer-ga4, server-side events) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: \"headers.origin\" }), or a static { value: \"example.com\" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt.',\n },\n 'extra-fields': {\n text: 'fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48).',\n },\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/schemas/index.ts","../src/schemas/settings.ts","../src/examples/index.ts","../src/examples/step.ts","../src/hints.ts"],"sourcesContent":["import { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\n\nexport { SettingsSchema, type Settings } from './settings';\nexport const settings = zodToSchema(SettingsSchema);\n","import { schemas, z } from '@walkeros/core/dev';\n\n// A field or input is a Mapping.Value (a path, a constant, a value config\n// or a fallback list), resolved via getMappingValue with { event, ingest }.\nconst FieldSchema = schemas.MappingSchemas.ValueSchema;\n\nconst InputSchema = z.union([\n FieldSchema,\n z.literal(false).describe('Switch this input off'),\n]);\n\n/**\n * Fingerprint transformer settings schema.\n *\n * Mirrors: types.ts FingerprintSettings\n */\nexport const SettingsSchema = z\n .object({\n salt: z\n .string()\n .optional()\n .describe(\n 'Secret key for the hash (HMAC), e.g. \"$env.FINGERPRINT_SALT\". Without it the hash can be reversed to the IP; a warning is logged.',\n ),\n rotate: z\n .enum(['daily', 'hourly', 'none'])\n .optional()\n .describe(\n 'Rotation window in UTC: the same visitor gets a new hash each window. Default: \"daily\"',\n ),\n ip: InputSchema.optional().describe(\n 'Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: \"ingest.ip\" when fields is not set',\n ),\n userAgent: InputSchema.optional().describe(\n 'User agent, reduced to browser, major version and OS before hashing. Default: \"ingest.userAgent\" when fields is not set',\n ),\n site: InputSchema.optional().describe(\n 'Site the visitor is on; a URL is reduced to its hostname. Default: \"event.source.url\" when fields is not set',\n ),\n fields: z\n .array(FieldSchema)\n .optional()\n .describe(\n 'Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly.',\n ),\n output: z\n .string()\n .optional()\n .describe(\n 'Dot-notation path where hash is stored on the event. Default: \"user.hash\"',\n ),\n length: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Truncate hash to this length. Default: full 64-char SHA-256 hash',\n ),\n })\n .describe(\n 'Fingerprint transformer: a cookieless, privacy-friendly visitor hash',\n );\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","export * as step from './step';\n","import type { Flow } from '@walkeros/core';\n\nexport const serverFingerprint: Flow.StepExample = {\n title: 'Server fingerprint',\n description:\n 'Default server fingerprint: the anonymized ingest.ip, the reduced ingest.userAgent and the site, keyed with settings.salt and rotated daily. ' +\n 'Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }. Requires source config.ingest. The hash depends on the salt and the day.',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Getting Started',\n id: '/docs/getting-started',\n },\n user: { hash: '158f99cc06e33fd6' },\n id: 'ev-1700000600',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000600,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const missingFields: Flow.StepExample = {\n public: false,\n description:\n 'Graceful handling when ingest is missing: each empty input logs one warning, and the hash is still generated.',\n in: {\n name: 'session start',\n data: { id: 's3ss10n' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'session start',\n data: { id: 's3ss10n' },\n user: { hash: 'e183220b699c10a8' },\n id: 'ev-1700000601',\n trigger: 'load',\n entity: 'session',\n action: 'start',\n timestamp: 1700000601,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n\nexport const ipAnonymization: Flow.StepExample = {\n title: 'IP anonymization',\n description:\n 'The ip input anonymizes the IP before hashing (IPv4 /24, IPv6 /48), so visitors from the same 10.0.42.* subnet ' +\n 'with the same browser, major version and OS share a hash within a day. No custom fn needed. Config: { salt: \"$env.FINGERPRINT_SALT\", length: 16 }',\n in: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n out: [\n [\n 'return',\n {\n event: {\n name: 'page view',\n data: {\n domain: 'www.example.com',\n title: 'Privacy Policy',\n id: '/privacy',\n },\n user: { hash: '44d9154b9a9b3792' },\n id: 'ev-1700000602',\n trigger: 'load',\n entity: 'page',\n action: 'view',\n timestamp: 1700000602,\n source: { type: 'express', platform: 'server' },\n },\n },\n ],\n ],\n};\n","import type { Hint } from '@walkeros/core';\n\nexport const hints: Hint.Hints = {\n 'ingest-prerequisite': {\n text: 'The default inputs ip and userAgent read ingest.ip and ingest.userAgent, so the server source must have config.ingest configured. config.ingest MUST use the map operator with direct request field paths (no req. prefix); a bare object like { ip: \"req.ip\" } is silently inert and leaves ingest empty. An input that resolves empty logs one warning (\"Fingerprint input ... resolved empty\") and the hash is still generated, but less distinct. Behind a load balancer the request ip is the balancer; read the client from headers.x-forwarded-for instead.',\n code: [\n {\n lang: 'json',\n code: JSON.stringify(\n {\n sources: {\n express: {\n package: '@walkeros/server-source-express',\n next: 'fingerprint',\n config: {\n settings: { port: 8080 },\n ingest: {\n map: {\n ip: { key: 'ip' },\n userAgent: { key: 'headers.user-agent' },\n },\n },\n },\n },\n },\n transformers: {\n fingerprint: {\n package: '@walkeros/server-transformer-fingerprint',\n config: {\n settings: { salt: '$env.FINGERPRINT_SALT' },\n },\n },\n },\n },\n null,\n 2,\n ),\n },\n ],\n },\n 'salt-and-rotation': {\n text: 'The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: \"none\" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: \"none\", ip: \"ingest.ip\", userAgent: \"ingest.userAgent\", site: \"event.source.url\", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: \"none\" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default.',\n },\n 'site-sources': {\n text: 'site defaults to the hostname of event.source.url, which the walkerOS browser and dataLayer sources set on every event and transformer-ga4 sets from the page URL of the hit (dl). Older transformer-ga4 versions did not set it, so upgrading transformer-ga4 changes the hash of GA4-decoded events once: site now joins the hash. Events without source.url (server-side events, GA4 hits without dl) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: \"headers.origin\" }), or a static { value: \"example.com\" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt.',\n },\n 'extra-fields': {\n text: 'fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48).',\n },\n};\n"],"mappings":"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","names":[]}
|
package/dist/walkerOS.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$meta": {
|
|
3
3
|
"package": "@walkeros/server-transformer-fingerprint",
|
|
4
|
-
"version": "4.7.0
|
|
4
|
+
"version": "4.7.0",
|
|
5
5
|
"type": "transformer",
|
|
6
6
|
"platform": [
|
|
7
7
|
"server"
|
|
8
8
|
],
|
|
9
|
+
"exports": {},
|
|
9
10
|
"docs": "https://www.walkeros.io/docs/transformers/fingerprint",
|
|
10
11
|
"source": "https://github.com/elbwalker/walkerOS/tree/main/packages/server/transformers/fingerprint/src"
|
|
11
12
|
},
|
|
@@ -31,30 +32,7 @@
|
|
|
31
32
|
"description": "Client IP, anonymized to /24 (IPv4) or /48 (IPv6) before hashing. Default: \"ingest.ip\" when fields is not set",
|
|
32
33
|
"anyOf": [
|
|
33
34
|
{
|
|
34
|
-
"
|
|
35
|
-
{
|
|
36
|
-
"type": "string",
|
|
37
|
-
"description": "Dot-notation path: \"ingest.ip\", \"event.data.userId\""
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"type": "object",
|
|
41
|
-
"properties": {
|
|
42
|
-
"key": {
|
|
43
|
-
"description": "Source property path",
|
|
44
|
-
"type": "string"
|
|
45
|
-
},
|
|
46
|
-
"value": {
|
|
47
|
-
"description": "Static value or fallback"
|
|
48
|
-
},
|
|
49
|
-
"fn": {
|
|
50
|
-
"description": "$code: function receiving { event, ingest } that returns the value. Runs only when no key is set.",
|
|
51
|
-
"type": "string"
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
"additionalProperties": false,
|
|
55
|
-
"description": "Mapping value config for computed fields"
|
|
56
|
-
}
|
|
57
|
-
]
|
|
35
|
+
"$ref": "#/definitions/MappingValue"
|
|
58
36
|
},
|
|
59
37
|
{
|
|
60
38
|
"type": "boolean",
|
|
@@ -67,30 +45,7 @@
|
|
|
67
45
|
"description": "User agent, reduced to browser, major version and OS before hashing. Default: \"ingest.userAgent\" when fields is not set",
|
|
68
46
|
"anyOf": [
|
|
69
47
|
{
|
|
70
|
-
"
|
|
71
|
-
{
|
|
72
|
-
"type": "string",
|
|
73
|
-
"description": "Dot-notation path: \"ingest.ip\", \"event.data.userId\""
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"type": "object",
|
|
77
|
-
"properties": {
|
|
78
|
-
"key": {
|
|
79
|
-
"description": "Source property path",
|
|
80
|
-
"type": "string"
|
|
81
|
-
},
|
|
82
|
-
"value": {
|
|
83
|
-
"description": "Static value or fallback"
|
|
84
|
-
},
|
|
85
|
-
"fn": {
|
|
86
|
-
"description": "$code: function receiving { event, ingest } that returns the value. Runs only when no key is set.",
|
|
87
|
-
"type": "string"
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
"additionalProperties": false,
|
|
91
|
-
"description": "Mapping value config for computed fields"
|
|
92
|
-
}
|
|
93
|
-
]
|
|
48
|
+
"$ref": "#/definitions/MappingValue"
|
|
94
49
|
},
|
|
95
50
|
{
|
|
96
51
|
"type": "boolean",
|
|
@@ -103,30 +58,7 @@
|
|
|
103
58
|
"description": "Site the visitor is on; a URL is reduced to its hostname. Default: \"event.source.url\" when fields is not set",
|
|
104
59
|
"anyOf": [
|
|
105
60
|
{
|
|
106
|
-
"
|
|
107
|
-
{
|
|
108
|
-
"type": "string",
|
|
109
|
-
"description": "Dot-notation path: \"ingest.ip\", \"event.data.userId\""
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"type": "object",
|
|
113
|
-
"properties": {
|
|
114
|
-
"key": {
|
|
115
|
-
"description": "Source property path",
|
|
116
|
-
"type": "string"
|
|
117
|
-
},
|
|
118
|
-
"value": {
|
|
119
|
-
"description": "Static value or fallback"
|
|
120
|
-
},
|
|
121
|
-
"fn": {
|
|
122
|
-
"description": "$code: function receiving { event, ingest } that returns the value. Runs only when no key is set.",
|
|
123
|
-
"type": "string"
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
"additionalProperties": false,
|
|
127
|
-
"description": "Mapping value config for computed fields"
|
|
128
|
-
}
|
|
129
|
-
]
|
|
61
|
+
"$ref": "#/definitions/MappingValue"
|
|
130
62
|
},
|
|
131
63
|
{
|
|
132
64
|
"type": "boolean",
|
|
@@ -139,30 +71,7 @@
|
|
|
139
71
|
"description": "Extra fields to include in hash (order matters). Each resolved via getMappingValue with source { event, ingest }. When set, named inputs are only used if set explicitly.",
|
|
140
72
|
"type": "array",
|
|
141
73
|
"items": {
|
|
142
|
-
"
|
|
143
|
-
{
|
|
144
|
-
"type": "string",
|
|
145
|
-
"description": "Dot-notation path: \"ingest.ip\", \"event.data.userId\""
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
"type": "object",
|
|
149
|
-
"properties": {
|
|
150
|
-
"key": {
|
|
151
|
-
"description": "Source property path",
|
|
152
|
-
"type": "string"
|
|
153
|
-
},
|
|
154
|
-
"value": {
|
|
155
|
-
"description": "Static value or fallback"
|
|
156
|
-
},
|
|
157
|
-
"fn": {
|
|
158
|
-
"description": "$code: function receiving { event, ingest } that returns the value. Runs only when no key is set.",
|
|
159
|
-
"type": "string"
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
"additionalProperties": false,
|
|
163
|
-
"description": "Mapping value config for computed fields"
|
|
164
|
-
}
|
|
165
|
-
]
|
|
74
|
+
"$ref": "#/definitions/MappingValue"
|
|
166
75
|
}
|
|
167
76
|
},
|
|
168
77
|
"output": {
|
|
@@ -177,7 +86,157 @@
|
|
|
177
86
|
}
|
|
178
87
|
},
|
|
179
88
|
"additionalProperties": false,
|
|
180
|
-
"description": "Fingerprint transformer: a cookieless, privacy-friendly visitor hash"
|
|
89
|
+
"description": "Fingerprint transformer: a cookieless, privacy-friendly visitor hash",
|
|
90
|
+
"definitions": {
|
|
91
|
+
"MappingValue": {
|
|
92
|
+
"title": "Mapping.Value",
|
|
93
|
+
"description": "Polymorphic transform primitive used in every mapping field. A string path, constant, operator object (map/loop/set/condition/consent), or array of values.",
|
|
94
|
+
"anyOf": [
|
|
95
|
+
{
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "String value or property path (e.g., \"data.id\")"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"type": "number",
|
|
101
|
+
"description": "Numeric value"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"type": "boolean",
|
|
105
|
+
"description": "Boolean value"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"description": "Value transformation configuration with multiple strategies",
|
|
109
|
+
"allOf": [
|
|
110
|
+
{
|
|
111
|
+
"$ref": "#/definitions/MappingValueConfig"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "array",
|
|
117
|
+
"items": {
|
|
118
|
+
"$ref": "#/definitions/MappingValue"
|
|
119
|
+
},
|
|
120
|
+
"description": "Array of values"
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"MappingMap": {
|
|
125
|
+
"title": "Mapping.Map",
|
|
126
|
+
"description": "Map: object mapping keys to transformation values.",
|
|
127
|
+
"type": "object",
|
|
128
|
+
"propertyNames": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
},
|
|
131
|
+
"additionalProperties": {
|
|
132
|
+
"$ref": "#/definitions/MappingValue"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"MappingLoop": {
|
|
136
|
+
"title": "Mapping.Loop",
|
|
137
|
+
"description": "Loop tuple [source, transform] for iterating and transforming arrays.",
|
|
138
|
+
"minItems": 2,
|
|
139
|
+
"maxItems": 2,
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": [
|
|
142
|
+
{
|
|
143
|
+
"$ref": "#/definitions/MappingValue"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"$ref": "#/definitions/MappingValue"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
"MappingSet": {
|
|
151
|
+
"title": "Mapping.Set",
|
|
152
|
+
"description": "Set: array of values for selection or combination.",
|
|
153
|
+
"type": "array",
|
|
154
|
+
"items": {
|
|
155
|
+
"$ref": "#/definitions/MappingValue"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"WalkerOSConsent": {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"propertyNames": {
|
|
161
|
+
"type": "string"
|
|
162
|
+
},
|
|
163
|
+
"additionalProperties": {
|
|
164
|
+
"type": "boolean"
|
|
165
|
+
},
|
|
166
|
+
"title": "WalkerOS.Consent",
|
|
167
|
+
"description": "Consent state mapping. Keys are consent groups (e.g. marketing, functional), values are booleans for granted/denied."
|
|
168
|
+
},
|
|
169
|
+
"MappingValueConfig": {
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"key": {
|
|
173
|
+
"description": "Property path to extract from event (e.g., \"data.id\", \"user.email\")",
|
|
174
|
+
"type": "string"
|
|
175
|
+
},
|
|
176
|
+
"value": {
|
|
177
|
+
"description": "Static primitive value",
|
|
178
|
+
"anyOf": [
|
|
179
|
+
{
|
|
180
|
+
"type": "string"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"type": "number"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"type": "boolean"
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"fn": {
|
|
191
|
+
"description": "Custom transformation function as string (serialized)",
|
|
192
|
+
"type": "string"
|
|
193
|
+
},
|
|
194
|
+
"map": {
|
|
195
|
+
"description": "Object mapping: transform event data to structured output",
|
|
196
|
+
"allOf": [
|
|
197
|
+
{
|
|
198
|
+
"$ref": "#/definitions/MappingMap"
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
"loop": {
|
|
203
|
+
"description": "Loop transformation: [source, transform] for array processing",
|
|
204
|
+
"allOf": [
|
|
205
|
+
{
|
|
206
|
+
"$ref": "#/definitions/MappingLoop"
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
"set": {
|
|
211
|
+
"description": "Set of values: combine or select from multiple values",
|
|
212
|
+
"allOf": [
|
|
213
|
+
{
|
|
214
|
+
"$ref": "#/definitions/MappingSet"
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
},
|
|
218
|
+
"consent": {
|
|
219
|
+
"description": "Required consent states to include this value",
|
|
220
|
+
"allOf": [
|
|
221
|
+
{
|
|
222
|
+
"$ref": "#/definitions/WalkerOSConsent"
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
"condition": {
|
|
227
|
+
"description": "Condition function as string: return true to include value",
|
|
228
|
+
"type": "string"
|
|
229
|
+
},
|
|
230
|
+
"validate": {
|
|
231
|
+
"description": "Validation function as string: return true if value is valid",
|
|
232
|
+
"type": "string"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"additionalProperties": false,
|
|
236
|
+
"title": "Mapping.ValueConfig",
|
|
237
|
+
"description": "Object-form value transformation with map/loop/set/condition/consent etc."
|
|
238
|
+
}
|
|
239
|
+
}
|
|
181
240
|
}
|
|
182
241
|
},
|
|
183
242
|
"examples": {
|
|
@@ -338,7 +397,7 @@
|
|
|
338
397
|
"text": "The salt keys the hash (HMAC), so it cannot be recomputed or reversed to the IP without it. Pass it via $env so it stays out of the flow file, and use one salt per deployment. Without a salt the transformer runs but logs a warning at startup. Rotation is built in (daily by default, UTC windows): do not add a date field for it. For another window, set rotate: \"none\" and add the window as a field. Setting fields switches the default inputs off, so name them too: { rotate: \"none\", ip: \"ingest.ip\", userAgent: \"ingest.userAgent\", site: \"event.source.url\", fields: [{ fn: () => new Date().toISOString().slice(0, 7) }] } rotates monthly. rotate: \"none\" without such a field keeps one hash per visitor indefinitely, which is a privacy decision, not a default."
|
|
339
398
|
},
|
|
340
399
|
"site-sources": {
|
|
341
|
-
"text": "site defaults to the hostname of event.source.url, which the walkerOS browser
|
|
400
|
+
"text": "site defaults to the hostname of event.source.url, which the walkerOS browser and dataLayer sources set on every event and transformer-ga4 sets from the page URL of the hit (dl). Older transformer-ga4 versions did not set it, so upgrading transformer-ga4 changes the hash of GA4-decoded events once: site now joins the hash. Events without source.url (server-side events, GA4 hits without dl) need site pointed elsewhere: an ingest path such as ingest.property (a path parameter) or ingest.origin (map it in config.ingest first, e.g. origin: { key: \"headers.origin\" }), or a static { value: \"example.com\" }. site keeps one visitor apart across sites when one collector with one salt serves several of them; a single-site deployment with its own salt is already separated by the salt."
|
|
342
401
|
},
|
|
343
402
|
"extra-fields": {
|
|
344
403
|
"text": "fields adds values to the hash as they resolve from { event, ingest }, in order. When fields is set, the named inputs ip, userAgent and site are only used if set explicitly, so an existing fields config keeps its input set. To transform a value before hashing, use fn alone: it receives { event, ingest } and returns the value. A fn next to a key never runs. The IP never needs a custom fn: the ip input anonymizes it (IPv4 /24, IPv6 /48)."
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkeros/server-transformer-fingerprint",
|
|
3
3
|
"description": "Fingerprint transformer for walkerOS server - hash configurable fields for session continuity",
|
|
4
|
-
"version": "4.7.0
|
|
4
|
+
"version": "4.7.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"update": "npx npm-check-updates -u && npm update"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@walkeros/core": "4.7.0
|
|
31
|
-
"@walkeros/server-core": "4.7.0
|
|
30
|
+
"@walkeros/core": "4.7.0",
|
|
31
|
+
"@walkeros/server-core": "4.7.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"url": "git+https://github.com/elbwalker/walkerOS.git",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
}
|
|
61
61
|
],
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@walkeros/core": "4.7.0
|
|
64
|
-
"@walkeros/server-core": "4.7.0
|
|
63
|
+
"@walkeros/core": "4.7.0",
|
|
64
|
+
"@walkeros/server-core": "4.7.0"
|
|
65
65
|
}
|
|
66
66
|
}
|