@scaletrail/connector 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (12.11.2026)
4
+
5
+ - `validateMessage('redact', …)`: `phone` nur noch in E.164 (`^\+[1-9][0-9]{6,14}$`). `email` zählt nur mit `@`
6
+ als Merkmal; ohne gültiges Merkmal braucht die Anfrage `order_ids` (eingeführt mit Schema `1.0.0-draft.8`;
7
+ ausgeliefert wird `draft.9`, siehe unten).
8
+ - Jeder Betrag (`*_cents`) höchstens `10000000000`, sonst ungültig.
9
+ - README: Löschanfrage mit allen Bestellungen der Kundin, Telefon in E.164, Beispiel.
10
+ - `add_to_cart` nimmt zwei optionale Felder: `dedup_id` (UUID aus dem Browser, dieselbe ID wie das Browser-Tag
11
+ desselben Klicks) und `consent` (Form wie an der Bestellung). `quantity` höchstens `10000`. Schema-Version
12
+ `1.0.0-draft.9`.
13
+ - Beispiele: `event-add-to-cart.json` mit `dedup_id` und `consent`, `event-login-identity.json` mit einer UUID als
14
+ `event_id` (je Login neu).
15
+ - README: Ereignisse sofort senden, `occurred_at` höchstens 300 s in der Zukunft, älter als 24 Stunden nicht mehr
16
+ berücksichtigt, `event_id` je Ereignis neu.
17
+
3
18
  ## 0.2.0 (11.11.2026)
4
19
 
5
20
  - **Signatur `v2`, nicht kompatibel mit 0.1.0.** Signiert werden Zweck, Zeitstempel, Pfad, Testmodus und Body.
package/README.md CHANGED
@@ -62,6 +62,23 @@ ScaleTrail warten.
62
62
  Ebenso: `sendEvent(event)` für `add_to_cart` und `login_identity`, `sendRedact(redact)`, wenn eine Kundin im Shop
63
63
  anonymisiert wird.
64
64
 
65
+ Ereignisse (`sendEvent`): sofort senden, `occurred_at` ist die Zeit des Ereignisses (höchstens 300 s nach der Uhr von
66
+ ScaleTrail, sonst `rejected`). Was älter als 24 Stunden ist, wird angenommen und nicht mehr berücksichtigt. `event_id`
67
+ ist je Ereignis eine neue `uuidV7()`, beim Wiederholen desselben Aufrufs dieselbe. Ohne `sa_uid` (Warenkorb und
68
+ Anmeldung) oder ohne gültige `email` (Anmeldung) wird das Ereignis angenommen und nicht berücksichtigt. Beim Warenkorb
69
+ optional `dedup_id` (Browser-ID aus dem Cookie `_sa_eid`) und `consent` (Stand der Einwilligung, Form wie an der
70
+ Bestellung): `statistics: false` oder `marketing: false` gelten für dieses Ereignis.
71
+
72
+ Löschanfrage (`sendRedact`): senden, **bevor** der Shop die Werte anonymisiert, und die Werte halten, bis `accepted`
73
+ zurückkommt. `order_ids` enthält **alle** Bestellungen der Kundin, die ihre Daten tragen, auch offene und unbezahlte.
74
+ `phone` nur in E.164 (`+41791234567`), sonst `rejected`. Mindestens eines von `email` (mit `@`), `phone` oder
75
+ `order_ids` muss gesetzt sein. `retry` (auch bei sehr vielen Bestellungen) heisst: dieselbe Anfrage wiederholen.
76
+
77
+ ```js
78
+ await client.sendRedact({ email: 'kundin@example.ch', phone: '+41791234567', order_ids: ['200123', '200456'],
79
+ requested_at: new Date().toISOString() });
80
+ ```
81
+
65
82
  ## Cookies am Server des Shops
66
83
 
67
84
  Der Server des Shops setzt bei **jeder** Antwort den Spiegel der Besucher-ID `_sa_uid_shop` und bei einer Landung mit
@@ -12,6 +12,8 @@
12
12
  "unit_price_cents": 4990,
13
13
  "currency": "CHF",
14
14
  "page_url": "https://www.example.ch/produkt/beispiel-serum/",
15
- "client": { "ip": "203.0.113.24", "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.1 Mobile/15E148 Safari/604.1" }
15
+ "client": { "ip": "203.0.113.24", "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.1 Mobile/15E148 Safari/604.1" },
16
+ "dedup_id": "0192f3b0-19f8-7a21-9c3e-2b4d6f8a0c1e",
17
+ "consent": { "source": "custom", "statistics": true, "marketing": true, "captured_at": "2025-12-04T09:31:15.000+01:00" }
16
18
  }
17
19
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "event": {
3
3
  "type": "login_identity",
4
- "event_id": "login_c-58210_2026-12-03",
4
+ "event_id": "0192f3ad-77c2-7e10-8b5a-4c6d8e0f1a23",
5
5
  "occurred_at": "2025-12-04T09:36:02.000+01:00",
6
6
  "sa_uid": "0192f3a4-5b6c-7d8e-9f01-23456789abcd",
7
7
  "customer_id": "c-58210",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scaletrail/connector",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Connect a custom-built shop to ScaleTrail: signed order snapshots, edge cookies, schema validation.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://scaletrail.io",
package/src/schema.js CHANGED
@@ -3,7 +3,7 @@ export const SCHEMA = Object.freeze({
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "$id": "https://api.scaletrail.io/connect/v1/schema.json",
5
5
  "title": "ScaleTrail Connector API v1 (Eigenbau-Shops)",
6
- "version": "1.0.0-draft.7",
6
+ "version": "1.0.0-draft.9",
7
7
  "oneOf": [
8
8
  {
9
9
  "$ref": "#/definitions/order_message"
@@ -18,14 +18,16 @@ export const SCHEMA = Object.freeze({
18
18
  "definitions": {
19
19
  "cents": {
20
20
  "type": "integer",
21
- "minimum": 0
21
+ "minimum": 0,
22
+ "maximum": 10000000000
22
23
  },
23
24
  "cents_or_null": {
24
25
  "type": [
25
26
  "integer",
26
27
  "null"
27
28
  ],
28
- "minimum": 0
29
+ "minimum": 0,
30
+ "maximum": 10000000000
29
31
  },
30
32
  "datetime": {
31
33
  "type": "string",
@@ -991,7 +993,8 @@ export const SCHEMA = Object.freeze({
991
993
  },
992
994
  "quantity": {
993
995
  "type": "integer",
994
- "minimum": 1
996
+ "minimum": 1,
997
+ "maximum": 10000
995
998
  },
996
999
  "unit_price_cents": {
997
1000
  "$ref": "#/definitions/cents"
@@ -1016,6 +1019,19 @@ export const SCHEMA = Object.freeze({
1016
1019
  "$ref": "#/definitions/client"
1017
1020
  }
1018
1021
  ]
1022
+ },
1023
+ "dedup_id": {
1024
+ "$ref": "#/definitions/uuid"
1025
+ },
1026
+ "consent": {
1027
+ "oneOf": [
1028
+ {
1029
+ "type": "null"
1030
+ },
1031
+ {
1032
+ "$ref": "#/definitions/consent"
1033
+ }
1034
+ ]
1019
1035
  }
1020
1036
  }
1021
1037
  },
@@ -1091,7 +1107,8 @@ export const SCHEMA = Object.freeze({
1091
1107
  "string",
1092
1108
  "null"
1093
1109
  ],
1094
- "maxLength": 32
1110
+ "maxLength": 32,
1111
+ "pattern": "^\\+[1-9][0-9]{6,14}$"
1095
1112
  },
1096
1113
  "order_ids": {
1097
1114
  "type": "array",
package/src/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Generated from the ScaleTrail Connector API v1 JSON Schema. Do not edit by hand.
2
- // Schema version 1.0.0-draft.7.
2
+ // Schema version 1.0.0-draft.9.
3
3
 
4
4
  export type Cents = number;
5
5
 
@@ -231,6 +231,8 @@ export interface EventAddToCart {
231
231
  currency: Currency;
232
232
  page_url?: string | null;
233
233
  client?: null | Client;
234
+ dedup_id?: Uuid;
235
+ consent?: null | Consent;
234
236
  }
235
237
 
236
238
  export interface EventLoginIdentity {
package/src/validate.js CHANGED
@@ -146,12 +146,15 @@ export function validateMessage(route, message, { strict = true } = {}) {
146
146
  return [...new Set(errors)].slice(0, MAX_PATHS);
147
147
  }
148
148
 
149
- // A redact request needs at least one of email, phone or a non-empty order_ids list: ScaleTrail finds the
150
- // customer by these, not by the shop's customer ID. The schema cannot express "at least one", the API checks it.
149
+ // A redact request needs at least one usable identifier: an email address (with "@"), a phone number in E.164
150
+ // (the schema checks the format), or a non-empty order_ids list. ScaleTrail finds the customer by these, not by
151
+ // the shop's customer ID. The schema cannot express "at least one", the API checks it. The API also rejects a
152
+ // phone number that is not valid for its country code.
151
153
  function redactHasIdentifier(redact) {
152
154
  if (!redact || typeof redact !== 'object') return false;
153
- const filled = (v) => typeof v === 'string' && v.trim() !== '';
154
- return filled(redact.email) || filled(redact.phone) || (Array.isArray(redact.order_ids) && redact.order_ids.length > 0);
155
+ const email = typeof redact.email === 'string' && redact.email.trim().includes('@');
156
+ const phone = typeof redact.phone === 'string' && redact.phone.trim() !== '';
157
+ return email || phone || (Array.isArray(redact.order_ids) && redact.order_ids.length > 0);
155
158
  }
156
159
 
157
160
  /** @param {unknown} message @param {{strict?: boolean}} [opts] */
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Kept in sync with package.json by a test.
2
- export const VERSION = '0.2.0';
2
+ export const VERSION = '0.3.0';