Skip to content

Release candidate - #83

Draft
jwrosewell wants to merge 61 commits into
mainfrom
rc
Draft

jwrosewell wants to merge 61 commits into
mainfrom
rc

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Releases what has collected on rc since the last release, #30, which merged on 4 November 2026 at 21:40 UTC.

This release moves Node.js toward one behavior across every 51Degrees language. Each element it changes now follows the published 51Degrees specifications, which in turn follow the RFCs they cite, so that the same input gives the same answer in .NET, Java, Node.js, Python, PHP, Go, Rust and C. The native modules take the C library release 51degrees-cxx 4.5.106, whose C code is that of 4.5.105.

Before you upgrade

  1. On-premise Device Detection defaults change. performanceProfile now defaults to 'Balanced', where it was 'LowMemory', and updateMatchedUserAgent is now on. Set performanceProfile: 'LowMemory' and updateMatchedUserAgent: false to keep the old behavior (#42).
  2. The JSON Builder needs a Sequence Element. It fails with "The JSON Builder needs a sequence number. Place a Sequence Element before the JSON Builder in the Pipeline." when there is none. PipelineBuilder now adds the Sequence Element first, so only a Pipeline built with addSequenceElement: false, or one that adds JSONBundlerElement by hand, is affected (#53, #74).
  3. The JSON sent to the browser gains a sequence entry, which holds session-id and sequence. A request with a session id and no sequence now gets sequence 1, where it got 2 (#59).
  4. The Cloud Request Engine sends more Evidence. It sends every Evidence value apart from the reserved names resource, license, licence and values, which it writes itself. Before, it sent only the Evidence that a Flow Element in the Pipeline accepted, so do not add Evidence that must not reach the cloud service (#72).
  5. Usage Sharing shares less. It shared every cookie, query string parameter and form field. It now shares a cookie only when its name starts 51D_, a parameter only when its name starts 51D_ or is in queryWhitelist, and never the Cookie header. Set shareAll: true to share as before (#35, #45).
  6. A Flow Element of your own must name its Evidence. An element with no evidenceKeyFilter accepted every key and now accepts none, and flowData.evidence.get gives undefined for a key that no element accepts. Give the element evidenceKeyFilter: new BasicListEvidenceKeyFilter([...]) with the keys it reads (#45).
  7. Data update log entries change. Each error entry starts Error during check for data update, so a log search or alert on the old text needs to change (#69).
  8. Latitude and Longitude have 3 decimal places, as do the points of WKT values such as Areas, so 18.542741172521133 is now 18.543 (#62).
  9. A client IP value is read only when all of it is one address. Values such as 1.2.3.4/24, 1.2.3.4, 5.6.7.8 and 01.2.3.4 were read and are now skipped. Pass one address in each value (#67).
  10. An Engine built with no properties keeps the first Data File's Properties across every data update. A Property that only a later Data File holds has no value until the Engine is created again (#55).
  11. Settings that were accepted now throw when the element is built. These are a JavaScript Builder protocol other than http or https, an objName such as window or document (#31, #60), and a Translation Engine behavior that names none of Original, EmptyString or FlowError (#49).
  12. A 51Did with white space inside it, or with characters from both base64 alphabets, is refused with the status INVALID_BASE64. FodId.toStandardBase64 returns null for such a text, so a TypeScript caller needs to handle null (#40, #56).
  13. Where one name is under more than one Evidence prefix, the Cloud Request Engine sends it once. It sends the value of the highest prefix in the order query, header, cookie, server, fiftyone, location, and compares prefixes and names ignoring letter case, so header.User-Agent and query.user-agent are sent as one field (#75).
  14. _51DEGREES_CLOUD_ENDPOINT replaces FOD_CLOUD_API_URL, which still works in this release. The Cloud Request Engine and the 51Did client read _51DEGREES_CLOUD_ENDPOINT, and read FOD_CLOUD_API_URL only when the new name is unset or empty. An engine whose endpoint came from the old name logs one warning to its Pipeline, and the first 51Did client that uses it raises one Node DeprecationWarning for the process. The baseURL and endpoint options still come first. Set _51DEGREES_CLOUD_ENDPOINT to the same value as the old name (#77, #88).

What changes

Web integration and client-side caching

  • Helpers.writeClientSideResponse writes the response of a client-side JavaScript or JSON endpoint with Cache-Control, Vary and an ETag. Node.js has no web integration package, so call it from your own endpoints, as the web examples do (#44).
  • The ETag is a weak entity tag, W/"...", and If-None-Match is compared weakly, as RFC 9110 section 8.8.3 and section 13.1.2 ask. A match gets 304 for GET and HEAD, and 412 for any other method. A response to a request whose processing failed has Cache-Control: private, no-cache and no ETag. The same Evidence gives the same ETag in every process (#44).
  • Evidence.addFromRequest adds the web request as a read-only view under server.http-request (#45).

JavaScript Builder

  • The JavaScript Builder checks the callback URL before it writes it into the client script. The protocol must be http or https, and the host must be ASCII labels or an IPv6 address in brackets, with no user information, query, fragment or zone identifier, following RFC 3986. A URL from the request that fails is left out of the script and logged (#31, #51).

Evidence

  • Evidence keys and Property names are compared ignoring letter case, so header.User-Agent and header.user-agent are one key, and flowData.device.get('IsMobile') works in any letter case (#71, #68).

Cloud

  • The Cloud Request Engine can authenticate with a License Key, alone or with a Resource Key, through licenseKey. With a License Key alone the Properties wanted must be named with requestedProperties. Both keys now travel in the request body (#64).
  • A License Key appears in log entries and error messages only as its last 4 characters, with the rest as *. The packages mask a key where one can enter a message, so the mask no longer runs on each detection, and Pipeline.log passes the message it is given to its listeners unchanged (#58, #85).
  • A cloud response that holds a 51Did is never stored in or served from the cache that cacheSize sets (#57).
  • The Cloud Request Engine fetches its accessible Properties and accepted Evidence keys before its first request, whatever else is in the Pipeline, so a Pipeline with a cache and no Cloud Engine serves a repeated request from the cache (#73).

Usage Sharing

  • After 10 failed sends in 100 seconds, sending pauses, and the pause doubles up to 300 seconds while sends keep failing. A new trackSession option tells sessions apart (#36, #35).
  • The record marks a value with replaced characters escaped="true", where it was replaced="true", and cuts a value at 10,000 code points, where it was 512 characters (#54).

Data updates

  • Data update log entries and errors use the text and levels of the specification's error table, which every language now shares. Every check reports a status, and an updateComplete listener that throws is logged (#69).
  • With updateOnStart and a License Key or an update URL, an on-premise Engine downloads a missing Data File, where the build threw. DataFileUpdateService.updateFromMemory is new (#52).
  • The file system watcher runs on its own fileSystemWatcher setting, whether automatic updates are on or not, and needs no License Key, as in the published packages (#52, #90).

Device Detection and IP Intelligence

  • Node.js reads each on-premise Property by name, because a read by name measured faster than a read by position, at 711 ns against 2,797 ns for one read of IsMobile on Ubuntu ARM with Node.js 22 (run 42995016804, #68).

Translation

  • TranslationEngine accepts the specification's option names, sourceElementDataKey and missingTranslationBehavior. sourceDataKey and behavior still work and are deprecated (#49).

C library

  • The C library moves from 51degrees-cxx 4.5.103 to 4.5.106, whose C code is that of 4.5.105. The HighPerformance profile holds in memory only what the named Properties read, and for 51D_GetHighEntropyValues and 51D_StructuredUserAgent the query value is used before the cookie value (#67, #55, #92).

Examples and documentation

  • Every on-premise example runs with no settings on the free Data Files, which it downloads the first time. A Property that only the Enterprise Data File holds is shown as needing that file, with a link to ask for a copy (#32).
  • The repository has one LICENSE file at its root, the EUPL 1.2 text, and every JavaScript source file carries the standard 51Degrees header. The fiftyone.geolocation package no longer carries a LICENSE file of its own (#79).

Environment variables

Each name below has a new name that starts _51DEGREES_, which every shell can set. The Cloud Request Engine and the 51Did client read the cloud endpoint, and the examples and tests read the rest. Every old name is still read in this release, but only when its new name is unset or empty, and its first use is reported as deprecated, through the Pipeline's logger or a DeprecationWarning in the packages, and in a printed message in the examples and tests. Each constant that held an old name holds it again and is marked @deprecated (#70, #77, #88).

Old name, deprecated New name
FOD_CLOUD_API_URL _51DEGREES_CLOUD_ENDPOINT
51DEGREES_DD_PATH _51DEGREES_DD_PATH
51DEGREES_IPI_PATH _51DEGREES_IPI_PATH
51DEGREES_RESOURCE_KEY, RESOURCE_KEY _51DEGREES_RESOURCE_KEY
LICENSE_KEY _51DEGREES_LICENSE_KEY
TEST_SUPER_RESOURCE_KEY, TEST_PLATFORM_RESOURCE_KEY, TEST_HARDWARE_RESOURCE_KEY, TEST_BROWSER_RESOURCE_KEY, TEST_NO_SETHEADER_RESOURCE_KEY _51DEGREES_RESOURCE_KEY_SUPER, _51DEGREES_RESOURCE_KEY_PLATFORM, _51DEGREES_RESOURCE_KEY_HARDWARE, _51DEGREES_RESOURCE_KEY_BROWSER, _51DEGREES_RESOURCE_KEY_NO_SETHEADER

Fixed

  • The Cloud Request Engine asked the cloud service for its Evidence keys and accessible Properties before every request, so each request made three calls to the cloud service in place of one. Release 4.5.102 and earlier have the fault (#73).
  • In 4.5.102 a Cloud Engine threw TypeError: Cannot convert undefined or null to object when the Resource Key did not include its product. One error is now logged for each Engine and the request carries on (#41).
  • An updateComplete listener that threw ended the Node.js process (#65).
  • The wait before a data update check used only the millisecond part of the Data File's expected update time, so polling did not wait for that time (#52).
  • Usage Sharing put a comma between the <Device> elements of a message, logged a failed send as Usage data sent, and wrote <ClientIP>undefined</ClientIP> for a request with no client IP (#35, #36, #54).
  • The IP Intelligence compare, metrics, suspicious and mixed examples stopped with RangeError: Maximum call stack size exceeded while they searched for a Data File (#32).

Issues this closes

Build

Full build 43368219214 is green, with macOS. It ran from 04:51 to 06:10 UTC on 13 November 2026 on tmp/pr-matrix-83rel4.

  • The head. All nine Setting 'PullRequestSha' lines, being Configure and the eight Build and Test jobs, read 8f14aa0459552aad9187539448bcc1e0b94dfce1, which is this pull request's head and the head of rc.
  • macOS is included, as a release build must be. Configure printed no "Left out N macOS entries" line, and macOS_Node_22 and macOS_Node_24 both ran and passed.
  • Every job is a success, being Configure, Build and Test on Ubuntu, Ubuntu ARM, Windows and macOS, each on Node.js 22 and 24, Compare Performance and Complete. Each Build and Test job ran 1,357 unit tests, with 1,355 passed and 2 skipped, and 136 integration tests, with 132 passed and 4 skipped. Report Compare Performance was skipped because Compare Performance passed.

Compare Performance (job 131724896677) passed on all four configurations it measures, against the last 10 results. The floor is the average less the larger of twice the standard deviation and 10 per cent of the average.

Detections per second This build Average of the last 10 Standard deviation Floor Change
Ubuntu_Node_24 31,391 33,088 7,512 18,064 -5.1 per cent
Ubuntu_ARM_Node_24 32,605 34,569 2,110 30,350 -5.7 per cent
Windows_Node_24 30,236 29,070 6,161 16,747 +4.0 per cent
macOS_Node_24 43,349 38,634 9,418 19,798 +12.2 per cent

Ubuntu_ARM_Node_24 measured 32,605, which is 0.93 of a standard deviation under its average and above its floor of 30,350. The changes run from 5.7 per cent down to 12.2 per cent up.

The release build before it, run 43335666904 on 20868944, passed every build and test job and failed the gate on Ubuntu_ARM_Node_24, at 30,352 against a floor of 30,510. #91 timed main against rc on one ARM runner, found rc slower beyond the spread of the rounds, and named the cause as the Evidence key filter that #53 made each on-premise Engine run for every detection, which put the filter's whole list in lower case on every check. #91 puts the list in lower case once, and keeps #53's filter, so each Engine is still passed only the Evidence it reads. It merged into rc at 04:50 UTC on 13 November 2026.

Against main. Run 43355023653, from #91, timed main, rc before #91 and #91's change on one ubuntu-22.04-arm runner, over nine rounds with the order turning. The medians were 33,711 detections a second for main, 30,437 for rc before #91 and 34,869 with #91, which measured higher than main in 7 of the 9 rounds. Apart from #91 itself, rc has since taken only #92, an automated submodule update merged at 04:12 UTC, which moves the C library to 51degrees-cxx 4.5.106, whose C code is that of 4.5.105, and gives the JavaScript templates a LICENSE file and a test. So this release is not slower than main on that runner.

Between 13ad4ebb and 20868944, rc took #85, #88, which reads every old environment variable name again, and #90, which runs the file system watcher on its own setting. It has also taken three changes to tests and CI alone, #82, #84 and #86, which is why they are not in the release note above.

Nine drafts still open into rc are later work and not in this release, being #89, #87, #66, #63, #46, #37, #27, #15 and #10.

Only the patch number moves.

jwrosewell and others added 30 commits November 3, 2026 18:37
The README now opens with what the code does in the words developers search for, then the install command, try-it links and, where there are any, the repositories it was created from. It ends with a Find out more section, and the logo is a file in the repository. Everything else the README said is kept.
The JavaScript bundler package was replaced by the JSON and JavaScript
builders in fiftyone.pipeline.core, which the pipeline Doxyfile already
reads. The location readme is README.md, and the documentation builds on
Linux, where file names are case sensitive.
The test that the cloud request engine puts the HTTP response into its
error asserted an ETag header on the cloud's 400 answer. Some cloud
servers send no ETag, so the test failed on macOS Node 24 in the
nightly build of #20 while passing on every other configuration. RFC
9110 section 6.6.1 requires a Date header in every 4xx answer from an
origin server with a clock, so the test now checks that one.
Co-authored-by: Automation51D <51DCI@51Degrees.com>
…ts (#40)

The 51Did specification says a package ignores white space before and
after the base64 text, refuses white space inside it, and refuses a text
holding a character from each alphabet, such as both + and -. The reader
trimmed with trim(), which also takes U+FEFF and leaves U+0085, left inner
white space to atob, which skips space, tab, line feed, form feed and
carriage return, and mapped - and _ to + and / with no check for a mix.

toStandardBase64 now trims every character with the Unicode White_Space
property and gives null for white space inside the text or for a mix,
checked before the mapping. FodId.tryParse and fromBase64 report that as
INVALID_BASE64, and the reader for web pages refuses it too. A value that
is empty or only white space is still MISSING_INPUT. The client's length
guard trims the same way, so it ignores what the reader ignores.

tests/base64.test.js covers the standard, URL-safe and unpadded forms of
a signed 51Did holding both + and /, with white space around and inside
the text and with each of the four mixes.
* Accept the specification's option names in the Translation Engine

The Translation Engine specification names the source key option
SourceElementDataKey and the missing translation option
MissingTranslationBehavior, with the values Original, EmptyString and
FlowError. The Node engine took sourceDataKey and behavior, and took the
behavior only as the enum's exact text, treating any other spelling or
any unknown value as Original without saying so.

The constructor now takes sourceElementDataKey and
missingTranslationBehavior, the specification's names in the option
style the package uses, and still takes sourceDataKey and behavior as
deprecated names, so no caller breaks. Where both names are given, the
specification's name wins. The behavior is the enum or text naming one
of its values in any case, so a configuration file can give it, and any
other value is refused when the engine is built. The IP Intelligence
country translation engines use the new names, and the package readme
shows them.

New tests cover the new names, the old names, precedence, each spelling
of each value, refused values, and a pipeline built from a configuration
with the new names.

* Keep the older option names in the IP Intelligence translation engines

The IP Intelligence translation package depends on the pipeline
translation package by a file path that publishing rewrites to a
version range, so a release of it can meet an older translation
release. An older release ignores sourceElementDataKey and
missingTranslationBehavior, which would leave the engines with no
source key. Every release accepts sourceDataKey and behavior, so the
two engines pass those again, as on rc, with a comment saying why. The
IP Intelligence package then needs no release for this change.

* Leave the IP Intelligence translation package as it is on rc

The two country translation engines keep passing sourceDataKey and
behavior, which every release of the translation package accepts, and
the comments saying so are removed, so that this change leaves the IP
Intelligence translation package exactly as it is on rc.
…te Matched User Agent (#42)

* Record the matched User-Agent by default, as the native code does

The 51Degrees specification and the C library both have the update
matched User-Agent option on by default, which fills the UserAgents
property. The Node engine and both pipeline builders defaulted it to
false and always passed it to the native configuration, so UserAgents
held only an empty string unless the caller turned the option on.

The engine now sets the option only when a value is given, and the
builders no longer default it, so the native default applies. False
still turns it off, from code or from a configuration file. The JSDoc,
the regenerated declarations and the match metrics example say it is
on by default.

New tests check that an engine and both builders left at their defaults
return the matched characters of a User-Agent from the free Lite data
file, and that false returns none. They are skipped with a message when
the Lite file is missing. The existing matched User-Agents test took an
end position as a length, which only passed while UserAgents was empty,
and now compares the right characters.

* Use the Balanced performance profile by default, as the native code does

The 51Degrees specification and the C library both make Balanced the
default performance profile for on-premise device detection. The Node
engine and both pipeline builders defaulted to LowMemory, so an engine
left at its defaults read every collection from the file with no cache.

The engine and both builders now default to Balanced, which the engine
maps to the native Balanced configuration through setBalanced. Any other
profile can still be chosen, from code or from a configuration file. The
JSDoc and the regenerated declarations say Balanced is the default.

Balanced holds more in memory than LowMemory, so anyone relying on the
default profile will use more memory after upgrading.

The engine does not expose the profile it uses, so the new tests watch
the profile setter the engine calls on the native configuration class
and read back the collection settings of that configuration. They cover
an engine and both builders left at their defaults, and choosing
LowMemory, and are skipped with a message when the Lite file is missing.

* Wait for the data file copy before the value tests run

The beforeAll in deviceDetectionOnPremiseValue.test.js copied the Lite
data file to Enterprise-HashV41.hash with the callback form of
fs.copyFile and returned without waiting. On a machine without that
file, the first test, Available Properties, opened it before the copy
finished and failed with "There is no file at", then passed on the next
run because the copy was by then on disk.

The beforeAll now awaits fs.promises.copyFile, so the file is there
before any test opens it, and a failed copy fails the suite instead of
being thrown from a callback. No other test in the repository starts a
copy without waiting for it.
…erprise data file (#32)

* Show what the Enterprise data file adds in the Device Detection examples

On the free Lite data file, which a fresh clone downloads, the getting
started example showed only the properties Lite has, and the web page
showed HardwareVendor, HardwareName and DeviceType as "Unknown (the
property 'hardwarevendor' is not in the results, so the current resource
key does not include it)", a message written for the cloud service.
Getting started, offline processing and both web pages now show each
property only the Enterprise data file has as needing the Enterprise data
file, with a link to ask for a copy. Metadata, match metrics and
performance end with what the Enterprise data file adds and the same link
when they run on the Lite file.

Without a licence key the data file update example threw "No license key
available" with a stack trace. It now shows the Lite data file it would
start from, says that an update needs a licence key, gives the link and
finishes. The automatic update examples link to the contact page for a
licence key, in place of pricing.

The shared helpers gain a check of whether the data file has a property,
read from the data file's own list rather than the engine's, the text and
notes the examples write, and a check of whether the data file is Lite.
The data file age warning added 30 to the published date as text rather
than adding 30 days, so it showed for a data file published four days
earlier, and it now links to the contact page. The free-file warning in
the data file log is removed, as each example now says what the
Enterprise data file adds in its own output. The file search recursed
through every folder up to the root of the disk and overflowed the stack
when the file was not there. It now keeps a list of the folders still to
search and skips any it cannot read. Downloading a missing data file uses
PowerShell 7 when it is on the path, and otherwise Windows PowerShell
without the module path it would inherit from PowerShell 7. Inside GitHub
Actions the examples no longer send their download source, so the data
repository's script names the build and test runs are not counted as
people using the examples.

* Show what the Enterprise data file adds in the IP Intelligence examples

On the free Lite data file, which a fresh clone downloads, getting started
and offline processing printed "Unknown (Property iprangestart not found
in ip)" for every property only the Enterprise data file has, and the web
pages did the same. They now show each one as needing the Enterprise data
file and give a link to ask for a copy. The web pages also show their
contact message, which they showed only for the tier 'Lite', a tier no IP
Intelligence data file has. Metadata and performance say what the
Enterprise data file adds.

Compare, metrics and suspicious looked for the Enterprise data file, and
the mixed example for a fuller Device Detection data file, before the Lite
ones, and each overflowed the stack searching the disk for a file that
was not there. They now use the Lite data files by default. Compare,
metrics, suspicious and translation need properties only the Enterprise
data file has, so with a free data file each says which, gives the link
and stops, where suspicious printed false and 0 as if they were answers
and translation listed every country alphabetically under most probable
first. The translation test returns early when the example stops.

The performance example downloads the free ASN data file when it finds no
data file, rather than the Lite file, and reads AsnName when the data file
has no RegisteredName. Without a licence key the data file update example
threw "No license key available". It now shows the ASN data file, says
that an update needs a licence key, gives the link and finishes. Its check
that stops an Enterprise data file being downloaded over a free one
compared the tier with 'Lite', and now checks for the Enterprise
properties. The automatic update examples link to the contact page for a
licence key.

The shared helpers change as the Device Detection ones do, and gain a
check of whether the data file is free and a stop for an example that
needs properties only the Enterprise data file has.

* Test the examples as a fresh clone runs them

CI puts the Enterprise IP Intelligence data file under the Lite file's
name, and a fuller Device Detection data file under the Lite name in the
tests folder, so nothing tested the free files a fresh clone uses. Each
package gains a test that runs every on-premise console example in its
own process, from its own folder, with the data file and licence key
settings removed, and checks that it finishes, shows what needs the
Enterprise data file and gives the link to ask for a copy. The web
examples are checked in the test process on the free files.

The Device Detection examples find the Lite data file themselves, in
cxx/data/device-detection, where CI links the Lite asset. The IP
Intelligence examples are given the free Lite data file, which CI now
fetches as the 51Degrees-LiteIpiV41.ipi asset and links into
cxx/data/ip-intelligence, and the performance and update examples find
the ASN data file themselves. Without an IP Intelligence key the Lite
asset is also linked under the Lite name, so it is not downloaded twice.
The tests run with the integration tests, which run every test under
examples.

* Keep two comments in the examples to what the code does

The comment on NEEDS_ENTERPRISE said every property the examples ask for
is in the Enterprise data file, which nothing in the examples checks. The
web page test gave the wrong reason for removing usage sharing, which is
removed so that the test sends nothing to 51Degrees.
* Fail the cloud tests rather than skip when no resource key is set

The live cloud tests skipped, or passed without calling the cloud, when
no resource key reached them, and some 51Did tests passed after printing
NOTHING PROVEN when the key got no marketing 51Did. A run with no key, or
with a key that lacks a product, therefore reported success.

Each test now reads _51DEGREES_RESOURCE_KEY, then
_51DEGREES_RESOURCE_KEY_BESPOKE, then the names it read before, and fails
with a message that names those variables and the product the key needs.
An answer that lacks what a test needs fails with the cloud's nullreason
and the product a key needs to get it. The IP Intelligence tests check the
key carries the ip product before they start. The console example tests
check what the example printed instead of expect(true).

IP Intelligence shared gains the key helpers and ExampleOutput that
Device Detection shared already has.

The Cloud 51Did workflow fails when no key reaches it. The Basic key
leaves the loop, because it is refused the marketing usages, and runs a
new tier test that checks it is given a non-marketing 51Did and refused
the marketing ones with the cloud's reason. The integration-test script
leaves the tier test out, as only that workflow has the Basic key.

* Read the shared key names in the location cloud tests, and name the consent usage

The location cloud tests read only RESOURCE_KEY, so a key set as
_51DEGREES_RESOURCE_KEY did not reach them. They now read the same
names in the same order as the other cloud tests, and fail with the same
message. The IP Intelligence shared types are the output of npm run tsc
for the new helpers.
…nce (#44)

* Test the caching headers of the client-side endpoints

The tests ask for the Cache-Control, Vary and weak ETag headers of the
web integration specification's cache header detail on both client-side
endpoints, the same ETag for the same evidence in two processes, a 304
for a matching If-None-Match on a GET and a 412 on a POST, as RFC 9110
section 13.1.2 requires, and private, no-cache with no ETag on a
response to a request that recorded errors, while a matching
If-None-Match still gets its 304.

* Send the client-side caching headers, and one ETag for the same evidence

Node's client-side JSON and JavaScript endpoints are written in each web
example, and none sent Cache-Control, Vary or an ETag, or answered
If-None-Match, so they had none of the web integration specification's
cache header detail.

Helpers.writeClientSideResponse writes either endpoint's response with
those headers. The ETag is a SHA-256 of the evidence the request
carries, read from the request again, so evidence processing adds, such
as the new session id the sequence element makes, takes no part, and
leaving out the request's precondition and cache headers, so that a
revalidation can match. The same evidence therefore gives the same ETag
in every process. It is weak, as RFC 9110 section 8.8.3 asks of a
validator that can stay the same when the response changes. A matching
If-None-Match gets a 304 with the same Cache-Control, Vary and ETag to a
GET or HEAD, and a 412 to any other method, as RFC 9110 sections 13.1.2
and 15.4.5 require. A response to a request whose processing recorded
errors carries Cache-Control private, no-cache and no ETag, and a
matching If-None-Match still gets its 304.

The web examples of Device Detection, IP Intelligence, location and the
pipeline's custom element example use it for every client-side endpoint.

* Regenerate the pipeline core types for writeClientSideResponse

* Test that the client-side ETag does not read the request after processing

A browser can stop waiting while the pipeline processes, and the
request's socket then has no addresses. Reading the request again for
the ETag then threw a TypeError, which stopped the Device Detection
cloud web example during the Selenium contract tests.

* Build the client-side ETag from what was read from the request

writeClientSideResponse read the request again for its ETag after
processing. A browser can stop waiting while the pipeline processes,
and the request's socket then has no addresses, so the read threw a
TypeError. In the Selenium contract tests on macOS this stopped the
Device Detection cloud web example.

addFromRequest now keeps every value it reads, under its evidence key,
in requestEvidence, and the ETag is built from that, hashing only the
evidence an element accepts, so it no longer touches the request.
Evidence added by hand has no such record, so its ETag is built from
the flow data's evidence.
…e file (#47)

* Show what the Enterprise data file adds in the Device Detection examples

On the free Lite data file, which a fresh clone downloads, the getting
started example showed only the properties Lite has, and the web page
showed HardwareVendor, HardwareName and DeviceType as "Unknown (the
property 'hardwarevendor' is not in the results, so the current resource
key does not include it)", a message written for the cloud service.
Getting started, offline processing and both web pages now show each
property only the Enterprise data file has as needing the Enterprise data
file, with a link to ask for a copy. Metadata, match metrics and
performance end with what the Enterprise data file adds and the same link
when they run on the Lite file.

Without a licence key the data file update example threw "No license key
available" with a stack trace. It now shows the Lite data file it would
start from, says that an update needs a licence key, gives the link and
finishes. The automatic update examples link to the contact page for a
licence key, in place of pricing.

The shared helpers gain a check of whether the data file has a property,
read from the data file's own list rather than the engine's, the text and
notes the examples write, and a check of whether the data file is Lite.
The data file age warning added 30 to the published date as text rather
than adding 30 days, so it showed for a data file published four days
earlier, and it now links to the contact page. The free-file warning in
the data file log is removed, as each example now says what the
Enterprise data file adds in its own output. The file search recursed
through every folder up to the root of the disk and overflowed the stack
when the file was not there. It now keeps a list of the folders still to
search and skips any it cannot read. Downloading a missing data file uses
PowerShell 7 when it is on the path, and otherwise Windows PowerShell
without the module path it would inherit from PowerShell 7. Inside GitHub
Actions the examples no longer send their download source, so the data
repository's script names the build and test runs are not counted as
people using the examples.

* Show what the Enterprise data file adds in the IP Intelligence examples

On the free Lite data file, which a fresh clone downloads, getting started
and offline processing printed "Unknown (Property iprangestart not found
in ip)" for every property only the Enterprise data file has, and the web
pages did the same. They now show each one as needing the Enterprise data
file and give a link to ask for a copy. The web pages also show their
contact message, which they showed only for the tier 'Lite', a tier no IP
Intelligence data file has. Metadata and performance say what the
Enterprise data file adds.

Compare, metrics and suspicious looked for the Enterprise data file, and
the mixed example for a fuller Device Detection data file, before the Lite
ones, and each overflowed the stack searching the disk for a file that
was not there. They now use the Lite data files by default. Compare,
metrics, suspicious and translation need properties only the Enterprise
data file has, so with a free data file each says which, gives the link
and stops, where suspicious printed false and 0 as if they were answers
and translation listed every country alphabetically under most probable
first. The translation test returns early when the example stops.

The performance example downloads the free ASN data file when it finds no
data file, rather than the Lite file, and reads AsnName when the data file
has no RegisteredName. Without a licence key the data file update example
threw "No license key available". It now shows the ASN data file, says
that an update needs a licence key, gives the link and finishes. Its check
that stops an Enterprise data file being downloaded over a free one
compared the tier with 'Lite', and now checks for the Enterprise
properties. The automatic update examples link to the contact page for a
licence key.

The shared helpers change as the Device Detection ones do, and gain a
check of whether the data file is free and a stop for an example that
needs properties only the Enterprise data file has.

* Test the examples as a fresh clone runs them

CI puts the Enterprise IP Intelligence data file under the Lite file's
name, and a fuller Device Detection data file under the Lite name in the
tests folder, so nothing tested the free files a fresh clone uses. Each
package gains a test that runs every on-premise console example in its
own process, from its own folder, with the data file and licence key
settings removed, and checks that it finishes, shows what needs the
Enterprise data file and gives the link to ask for a copy. The web
examples are checked in the test process on the free files.

The Device Detection examples find the Lite data file themselves, in
cxx/data/device-detection, where CI links the Lite asset. The IP
Intelligence examples are given the free Lite data file, which CI now
fetches as the 51Degrees-LiteIpiV41.ipi asset and links into
cxx/data/ip-intelligence, and the performance and update examples find
the ASN data file themselves. Without an IP Intelligence key the Lite
asset is also linked under the Lite name, so it is not downloaded twice.
The tests run with the integration tests, which run every test under
examples.

* Keep two comments in the examples to what the code does

The comment on NEEDS_ENTERPRISE said every property the examples ask for
is in the Enterprise data file, which nothing in the examples checks. The
web page test gave the wrong reason for removing usage sharing, which is
removed so that the test sends nothing to 51Degrees.

* Use LowMemory in the IP Intelligence examples built for the Enterprise file

The Enterprise IP Intelligence data file is over 6 GB, and the
MaxPerformance profile holds all of it in memory, so an example that
picks the Enterprise file needed that much free memory before its
first lookup.

The examples built for the Enterprise file, or that pick it when they
can find it, now use LowMemory, which reads the file as it goes. These
are the suspicious and update data file examples, the three automatic
update examples, which download the Enterprise file with a licence key,
and the IP Intelligence engine of the mixed console and API examples,
whose device detection engine is unchanged. The metrics and compare
examples default to LowMemory and take a fourth command line argument,
and a matching parameter of run, that names another profile, such as
MaxPerformance for speed where the machine has the memory.

Examples that load every property from the Lite file, the readme quick
starts, the performance example and the device detection examples keep
their profiles.

* Use LowMemory in country translation and MaxPerformance in the compare test

The country translation example needs the Enterprise data file, so it moves
from Balanced to LowMemory like the other examples built for that file.

The compare test names MaxPerformance, because CI gives it the Enterprise
file and holds all of it in memory, where LowMemory looks up about a third
as many records a second.

The compare header now says the fourth command line argument names the
profile, as the metrics header does, and the metrics header says
"command line argument" where it said "parameter".
)

* Test the cloud engine against answers with a missing or null element

The tests run the base CloudEngine.processInternal, which the existing
error message tests replace, against a mocked cloud answer with no key
for the engine's data key, with a null element and a reason, and with a
null element and no reason.

* Log a missing cloud element plainly instead of throwing a TypeError

A resource key that does not include a product gets a cloud answer with
no key for that product at all. CloudEngine.processInternal only tested
for null, so it went on to Object.entries(undefined) and every engine
that keeps the base method threw "Cannot convert undefined or null to
object", which says nothing about the key.

A missing element is now logged once per engine, saying the resource
key does not include the product, and the request carries on with empty
element data, so reading a property gives the missing property
message. The Cloud Request Engine specification asks for this when the
credential does not cover what was asked for, in "Properties the remote
server did not return".

A null element is still logged on every request. The log message had
its ternary binding after the concatenation, so only the reason was
logged, and "undefined" when there was none. It now has the
"not populated." prefix and a fallback when the cloud gives no reason.

* Regenerate the cloud request engine types for the new messages

* Say which answers the missing element tests cover
* Check the callback URL as the specification's six rules say

Refuse an IPv6 zone identifier, an '@', '?' or '#' anywhere in the host value, and a name ending in a number unless it is an IPv4 address in dotted decimal form. The logged URL shows *** in place of everything in the host before its last '@'.

* Test that a port which is present is not empty

* Refuse a port that is present but empty

A host such as https://example.com otherwise reads as the host https with an empty port. RFC 3986 section 3.2.3 allows an empty port, but section 6.2.3 treats it as equal to none, so the callback URL does not carry one.
… assets'

Co-authored-by: Automation51D <51DCI@51Degrees.com>
* Add tests that license keys are masked in log entries and error messages

* Mask license keys in log entries and error messages

The Logging page says a license key must not appear in full in a log entry or
an error message, and that only its last 4 characters are shown, with every
character before them replaced, including in a URL.

* Add tests that a license key the cloud names is masked everywhere it goes

The cloud refuses a license key with an error that names the key in
full, so the tests give the cloud request engine and the 51Did client
that answer and check the log entries, the flow data errors and the
rejected errors. They also check text inside objects, arrays and an
error's other properties, a key followed by a full stop, and keys joined
by a bar that the URL formatters write as %7C.

* Mask a license key the cloud names, and inside objects and errors

The cloud refuses a license key with an error that names the key, for
example "Invalid license key(s) supplied: '<key>'". The mask found a key
only after a parameter name, so that key reached the log entries,
flowData.errors and the rejected errors in full.

- LicenseKeys.mask takes the known keys as a second argument and masks
  each wherever it appears, as written or encoded for a URL. A value
  holding several keys separated by a comma or a bar is masked as each.
- The cloud request engine masks its own license key in the text of
  every error it makes from the cloud's answer, before the error is
  logged, rejected or kept on the flow data.
- The 51Did client masks its licence key in the message and body of
  each redeem error. The 51Did package depends on nothing in the
  Pipeline, so it has its own short mask.
- The mask now reaches text inside arrays and plain objects, and every
  own property of an error, including the errors and the cause it holds.
- A parameter's value stops before punctuation that ends a sentence, so
  ABCDEFGH followed by a full stop gives ****EFGH and the full stop.
- %7C, the encoded bar the URL formatters write, separates keys.
* Add tests for the data update behaviour the specification sets

* Update the data file as the specification says

The Data update page of the pipeline specification sets the
If-Modified-Since fallback, when the first check is made, update on
start-up with no data file, programmatic update of a memory data file and
the log messages.

* Add tests for the watcher gate, one status per update and midnight UTC dates

* Create the file system watcher only while autoUpdate is on, report one status for every update, and read engine dates as midnight UTC

The Data update page says no file system watcher is created with autoUpdate
disabled. A bad update URL, a download that is cut short and a failed copy
or refresh now each report one status with the data file no longer marked as
updating, and a download that cannot be removed after a good refresh is
logged and reported as a success.

* Add tests for a reset download, a locked file, a failed read and no published date

A connection reset part way through the download must be reported once
and schedule one check. A locked data file, a download that cannot be
read, and an MD5 check that cannot read the download are each reported
once. An on-premise engine with no data yet asks for the file with no
If-Modified-Since header.

* Report each data update once when the connection resets during the download

A reset raises an error on the request whilst the response is still
arriving, and then closes the response. Both reported the update and
each called checkNextUpdate, so the data file then polled on two
timers. Each update now ends once, and an error on the request after
the response has arrived is left to the response's handlers, which
report it as a failed download. The MD5 check's read stream has an
error listener, so a failed read is reported once rather than raising
an unhandled error.

* Leave the one report to the response's handlers without a second flag

Once the response has arrived, the request's error handler does nothing,
and the output's error handler now checks written as the response's
close handler does, so either reports a failed download and the other
does nothing. That covers every case the separate ended flag did.
* Add tests for a 51Did factor word the package does not know

* Report a 51Did factor word the package does not know as a mismatch

The Package surface page of the 51Did specification says a factor word a
package does not know must be reported as a mismatch, so it never reads as a
factor that matched or one that could not be compared. The words as sent
stay available as factorsRaw.

* Update the test that kept an unknown factor word to the mismatch rule

* Change the 51Did factor tests to read words in any case and null as a mismatch

The specification says the comparison of a factor word ignores case, as
.NET does, and that an unexpected value never reads as one that could
not be compared, so null is a mismatch, as in .NET and Java. The words as
sent are in the raw body, so the tests no longer ask for factorsRaw. The
redeemed mismatch test sent null for one factor and expected it back, so
it now sends notrecorded.

* Read 51Did factor words in any case and null as a mismatch, and drop factorsRaw

The specification says the comparison of a factor word ignores case, as
.NET does, so a word is lower-cased before it is compared. A factor value
that is not a string, null included, is a mismatch, as in .NET and Java,
because the outcome table has no null and an unexpected value must never
read as one that could not be compared. The new public factorsRaw added
to the 51Did surface in Node alone, which the specification says changes
in all six packages together, and the body as sent is already public as
raw, so it is removed.
* Add tests that the cloud request engine never caches a response holding a 51Did

* Never cache or serve a cloud response that holds a 51Did

The Caching page says a cache must not store a result that holds a value
created for one request, and must not serve one it finds. For the Cloud
Request Engine that is a response with a top level fodid member.

* Add tests for a fodid member in another case and for a cached 51Did

The specification matches the member name fodid in any case. A test
also puts a response holding a fodid into the cache and checks that the
request goes to the cloud and gets the cloud's answer, which covers the
rule that a cache must not serve such a result that it finds.

* Match a top level fodid member in any case and work it out once per result

The specification says the member name fodid is matched in any case, as
.NET does, so a response with FodId or FODID was stored and served. Each
cache hit also parsed the whole response again to answer isCacheable.
The answer is now kept for each element data, and a response whose text
holds no fodid in any case is not parsed at all.
* Test that a data update listener that throws is logged and goes no further

The update status is reported from timer, request and file system
watcher callbacks, where an error thrown by an application's
updateComplete listener would be uncaught and end the process. The
error must be logged, and the other listeners must still be called.

* Log an error thrown by a data update listener rather than end the process

complete called the updateComplete listeners through emit with no catch,
so a listener that threw inside a timer, HTTP or fs.watch callback was an
uncaught exception that ended the Node process. Each listener is now
called in turn and an error it throws is logged, as an error thrown by
onUpdateComplete already was. The raw listeners are called, so a once
listener is still removed after it runs.

* Expect the update complete callback error message the specification sets

* Log an update complete callback error in the words the specification sets

* Expect the data update messages and levels the .NET service writes

The Errors table of the pipeline specification now follows the .NET data
update service, so the tests ask for its text, its levels and its statuses.
A connection failure, a bad response, a failed integrity check and a failed
refresh become errors rather than warnings, a response with no Content-MD5
header becomes a warning that lets the check go on, and the missing
temporary path is reported as its own status. A locked data file is the
same row as any other failure to copy, and a failure with no row of its own
is the unhandled error. Three further groups cover the statuses .NET
reports that Node has no name for, a check that found nothing to do
reporting not needed, and a second check while one runs reporting in
progress.

Tests only, so the run from this commit is red.

* Write the data update messages and levels the .NET service writes

Each event the Errors table of the pipeline specification lists now carries
the text, the level and the status the .NET data update service gives it,
so a reader of a Node log and a reader of a .NET log see the same sentence
for the same event.

A connection failure, a URL that will not parse, a download cut short, a
429, a 403, any other response that is not a success or a 304, a failed
integrity check and a failed refresh are logged at error rather than
warning, and each names the update service and the engine as .NET does. The
retry sentence goes, because no .NET message carries one. A response with
no Content-MD5 header is a new warning that lets the update go on, where
Node treated the missing header as a mismatch and stopped. A data file with
no temporary path is reported as the temp path status rather than the
reading stream one, and a locked data file is the same row as any other
failure to copy. An error with no row of its own is logged as the unhandled
one and reported as the unknown error status.

The status list gains the four names .NET reports that Node had no name
for, which are the temp path, timeout, no configuration and unknown error
ones. A check with no update URL reports not needed, and a check started
while one is still running reports in progress, so every check is reported
once.

* Say the callback error wording is the one Python uses

The comment on callbackErrorText said every language uses this wording. Python does, and .NET has no message naming the callback, so the comment now says Python.

* Leave out the error parameter the three data update error handlers no longer read

The connection, write and integrity check handlers now log the sentence .NET writes, which carries no error detail, so each err parameter went unread and lint failed on n/handle-callback-err.

* Expect each data update error to carry its cause after the .NET text

* Log the cause of each data update error after the .NET text, as .NET attaches the exception

* Expect .NET's unhandled error text for a callback error, and the console where there is no pipeline

* Log a callback error in .NET's words with its cause, and write an error to the console where there is no pipeline

* Send a hash in the MD5 read failure test, as the check is made only where a response carries one

* Expect the callbacks after one that raises an error not to be called

* Stop at the first update complete callback that raises an error, as the specification and .NET do

* Expect each error in a row of its own to be logged as Error during check for data update, with the row's text and its cause

* Log each error in a row of its own as Error during check for data update, with the row's text and its cause, as .NET does

* Expect an error while the next check is scheduled to be logged with the unhandled error message and its cause

* Log an error while the next check is scheduled with the unhandled error message and its cause

* Expect no status that nothing in the service reports

* Leave out the two statuses nothing in the service reports

* Declare the two statuses the service now reports in its types
…very read (#68)

* Test that each property is read by its build-time index

A property's index is its place in the list of names the engine was built
with, fixed at build, so the same index names the same property after any
reload. These tests cover both on-premise engines.

The tests that drive the Element Data with a stand in for the native results
check that each read asks by index, that a name outside the build-time list
falls back to a read by name, and that an engine built with no names keeps
reading by name as it does today. The IP Intelligence tests also cover each
weighted getter and the order of the arguments the WKT getter takes.

The tests that use the native engine and a real data file check that the name
at each index is the name the engine was built with, that two engines built
with the same names in different orders read the same values, that a superset
of the built names reads the same values, and that a read by name and a typed
read agree. Two of them print how long a read by name and a read by index
take.

This commit holds the tests alone, so they fail until the change that follows.

* Read each property by its build-time index

Both on-premise engines read every value by name, so the native code searches
its sorted list of property names on every read. A property's index is its
place in the list of names the engine was built with, which the native code
fixes at build, so the same index names the same property after any reload
whatever order the reloaded data set holds.

Each engine now keeps the names it was built with and a table from each name
in lower case to its place in that list, made once in the constructor and
never from the data set. The data set's own property list is sorted by name,
not held in index order, so it can never be the source of an index. The
Element Data read looks the name up in that table and passes the index to the
native getter, which has an index overload of every getter the engines use.

A property the engine was not built with has no index, so its read goes by
name and the native code gives its own no value message. An engine built with
no property names has an empty table and reads by name exactly as it did,
which keeps today's behaviour until the half of the rule that covers it is
taken up.

* Read the Element Data from the Flow Data after the wait in the native index tests

The helper gave the Element Data to a promise, which asks for its then property, and the Element Data throws for a property it does not hold, so all seven native tests failed with "Property then not found in device". The helper now gives back the Flow Data and each test reads device from it.

* Test that restricted names ignore case and that a name such as constructor gets its index

Adds a diagnostic native timing that varies the stack trace limit, to find
where the extra cost of a read by index goes. The diagnostic is removed
once the cause is shown.

* Use single quotes in the diagnostic timing labels

* Compare restricted property names ignoring case, and give the index table no prototype

access-to-results.md says string comparisons of keys must be case
insensitive. AspectData.get compared the key with the restricted list as
given, so an engine built with IsMobile refused ismobile. The engine now
keeps the restricted names in lower case as a set.

The index table is made with Object.create(null), so a property named
constructor is not found on the prototype before it is written.

* Vary the native context's stack limit and the stack depth in the diagnostic timing

* Remove the diagnostic timing

* Take out the read by index, which is slower through Node's wrapper

Each native getter has an overload taking the name and one taking the
index, and SWIG's V8 dispatcher tries the name first. Given a number, that
attempt fails and makes an Error with a stack trace before the index
overload runs, so a read by index cost 2,797 ns against 711 ns by name.
Node keeps reading by name until the wrappers are regenerated without that
cost. The restricted name case fix and its test stay.

* Test the restricted name case fix with the native engine, and declare the lower case names in the types

* Remove a second blank line after the licence header

* Test that a read finds a property whatever the letter case of its key

Covers the core and engines dictionaries, the Device Detection Element Data
with the native engine, including a match metric, and the IP Intelligence
Element Data with a stand-in for the native results.

* Find a property whatever the letter case of its key

access-to-results.md says string comparisons of keys must be case
insensitive. Each read still looks the key up as given first, so a read
that found its value before costs no more. Only a key that finds nothing
is tried again.

- The core and engines dictionaries search their contents ignoring case,
  because the contents can change after the data is made.
- Device Detection maps each property key in lower case to the key itself
  when the data file loads, because the match metric keys are not all in
  lower case.
- IP Intelligence keys every property in lower case already, so the key is
  tried in lower case.

* Time a read by name before and after the letter case change

* Look a key up ignoring case only after it finds nothing as given

The first version changed each getInternal, and in full build 43006578086
a dictionary read took 210.4 ns against 190.4 ns before. ElementData.get
and AspectData.get now call getInternalIgnoringCase only when getInternal
finds nothing, so each getInternal is as it was and a read that finds its
value runs the same code as before. Each Element Data class gives its own
getInternalIgnoringCase, and the timing tests take the earlier behaviour
by giving one that finds nothing. Each timing now takes the fastest of 10
rounds and changes which goes first.

* Test that an element restricted without an engine keeps its lower case names

* Keep the lower case restricted names on an element that has none, rather than make them on every read
…t name (#60)

* Test that the object name refuses the Window properties the specification lists

* Refuse the Window property names the specification lists as the object name

* Wrap the comment in the callback URL test to 80 columns

* Name arguments and location in the reserved names comment

The comment skipped arguments and read location as one of the Window
properties a top level var cannot replace, where location is refused for
a different reason, being that a top level var navigates the page away.
Python, Go and Rust already say both.
* Test that usage sharing pauses sending after failed sends

Usage sharing in Node has no back-off. Every package is sent while the
service is failing, and is lost. This test expects the back-off .NET is
being given, at its default settings of ten failed sends within 100
seconds and a first pause of two seconds. After ten failed sends nothing
is sent during the pause, and the evidence processed during it is all
sent once the pause ends and the service answers, with no more requests
processed.

The test has its own receiver on another port, which can answer with any
status. It fails until the back-off is added.

* Pause usage sharing sends after repeated failures

Node now has the same back-off as .NET, with the same five settings and
defaults, as options.

- After failuresToEnterRecovery sends fail within failuresWindowSeconds,
  sending pauses for initialRecoveryDelaySeconds. A send fails when the
  request errors, has no answer within requestTimeout, or the answer is
  not 200.
- When a pause ends one send is tried. If it fails the delay is
  multiplied by recoveryMultiplier, up to maxRecoveryDelaySeconds. If it
  succeeds the back-off ends.
- Processing is not affected. Evidence is queued during a pause, and new
  evidence is dropped only when the queue is full. The new
  maximumQueueSize option sets the size, by default 1000 or ten times
  the requested package size if that is larger, as in .NET. The queue had
  no limit before, which a pause would have needed.
- One message is sent at a time, as in .NET and Java, until fewer than
  requestedPackageSize entries are queued. A timer that does not keep
  the process running starts sending when a pause ends, so the queue
  drains without more requests being processed.
- With one message at a time, a request that never answered would stop
  sending, so the new requestTimeout option, 60 seconds by default as in
  Python, ends such a request and counts it as a failed send.

ShareUsageBackOff holds the rules and takes the time from the caller, so
its tests set the time. The type declarations list the new options.

* Test the full queue log, the failed answer log, message size, the comma, a throwing request, the clock and the repeat boundary

* Log a full queue and the content of a failed answer, send up to twice the package size, join entries without commas, time pauses on a monotonic clock and share a repeat at the interval

* Update the usage sharing types

* Look for the warning that carries the status code in the failed answer test
…cloud (#61)

* Test that reserved names in Evidence are not sent in the request

* Send only the Evidence the cloud accepts, never resource, license, licence or values

* Tidy the reserved Evidence names change after review

* Expect the engine's own keys in the body in the reserved Evidence names test

rc now sends the resource key and license key in the request body, set after the Evidence, so the test expects those two fields with the engine's values, no license in the URL, and no other field whose name is a reserved one.

* Say what the reserved Evidence names are in the Cloud Request Engine

* Test that a reserved name with no prefix is left out of the request
* Add tests for evidence keys ignoring letter case

The Evidence page of the pipeline specification says keys MUST be
case-insensitive, so header.User-Agent and header.user-agent are the
same key. Node stores and reads a key exactly as given, so the two are
two separate entries today.

These tests fail without the fix that follows. They cover reading a key
in any case, a key added again in other letters replacing the value
rather than adding a second entry, the first spelling being kept, and an
element reading evidence a caller added in other letters.

* Read and store evidence keys without regard to letter case

The Evidence page of the pipeline specification says keys MUST be
case-insensitive, so header.User-Agent and header.user-agent are the
same key. Node stored and read a key exactly as given, so the two
spellings became two entries and a caller that added one and read the
other got undefined. Header names arrive in whatever letters the client
or the framework used, whilst our elements read a fixed lower-case key,
so evidence that was supplied could go unseen.

Evidence now keeps a map from each key in lower case to the key the
value is held under. add reuses the spelling a key was first added with,
so a later add replaces the value rather than adding a second entry, and
get finds the value from any spelling. The map is written only where the
value is actually stored, so a key the element filters reject leaves
nothing behind.

The element filters already compared without regard to case, so they are
unchanged. types/evidence.d.ts follows the source.

This matches the shape chosen in Python and PHP.

* Test that the JavaScript Builder keeps an evidence key added in other letters

The JavaScript Builder's filter looks for query. and header. in the letters given, so on a pipeline where it is the only element a key such as Query.Client-IP is thrown away before get can be asked for it. This test fails until the filter ignores letter case.

* Let the JavaScript Builder keep an evidence key in any letters

The JavaScript Builder's evidence filter now lower-cases the key before looking for query. and header., as the basic list and usage sharing filters already do, so whether a key is kept no longer depends on its letters. The table from each key in lower case to its stored spelling is made with no prototype, so a key such as __proto__ or constructor is an ordinary entry in it. The evidence store itself keeps its prototype, because getAll() hands it to callers who may call its methods.
…ix the README links (#33)

* Show the root logo in every README, add one contributing guide, and fix the README links

Twenty READMEs and example guides showed an older 51Degrees wordmark from
51degrees.com rather than the logo the root README shows, and the
fiftyone.pipeline.did package README had none. Each now shows the root logo
as a Markdown image from raw.githubusercontent.com, using
.github/images/logo-160.png, a 160 pixel copy of .github/images/logo.png,
the size the root README shows it at.

The repository had no contributing guide of its own. One CONTRIBUTING.md
at the root now links the 51Degrees contributing guide.

The Developer Documentation link at the top of twenty READMEs and example
guides went to the main page of the product's generated API reference,
which has no content. They now go to the product's documentation, and the
root README's table links both that and the class list of the Node.js API
reference. The dependencies page that the Device Detection and IP
Intelligence READMEs linked is gone. The IP Intelligence README's next
sentence already links the tested versions page, so the sentence is
removed, and the Device Detection README links the tested versions page in
its place. The translation package's link to the on-premise package
resolved nowhere, and now goes to it on GitHub, as npm cannot follow a
relative link.

* Link each product's quick start from the READMEs

The documentation links went to section pages of the 51Degrees
documentation, such as _device_detection__index.html, which only list the
pages in the section. They now go to the page in each section that is most
helpful to a developer and the most recently kept up to date. Those are the
Device Detection, IP Intelligence and Reverse Geocoding quick starts, which
show how to choose between the cloud and on-premise and how to start in
each language, and Pipeline Basics for the Pipeline API, which has no quick
start. The campaign tags of each link are unchanged.
…ber (#74)

* Test the exact JSON Builder error text with no sequence number

* Give the specification's JSON Builder error text with no sequence number

Every language gives the same text when no Sequence Element has produced a sequence number, as the JSON Builder page of the specification states.
…#70)

* Give every environment variable one name with no fallback

A POSIX shell cannot set a variable whose name starts with a digit, so
51DEGREES_DD_PATH, 51DEGREES_IPI_PATH and 51DEGREES_RESOURCE_KEY could
not be exported the way the documentation told a developer to export
them. Several other variables were read under two names, a _51DEGREES_
one first and an older one behind it, which is a second thing to set, a
second thing to document and a second thing to get wrong.

Every variable is now read as _51DEGREES_<NAME> and under no other
name. RESOURCE_KEY and LICENSE_KEY go as well, although a shell can set
them, because one name with no fallback is the rule whatever the old
name looked like.

Changes 165 lines naming an old variable across 67 files, covering the
example helpers, the example sources and their comments, the shared
test key helpers and their type declarations, the product readmes, the
CI scripts and AGENTS.md.

Removes the machinery a fallback needed, being the legacyEnvVars map
and the resourceKeyNames helper in the device detection shared tests,
LEGACY_RESOURCE_KEY_ENV_VAR and its second read in both cloud example
helpers, RESOURCE_KEY from three ordered name lists, and the dead
RESOURCE_KEY and TEST_*_RESOURCE_KEY exports from the CI scripts.
Renames the bare RESOURCE_KEY in the location examples and the bare
LICENSE_KEY in the automatic update examples, neither of which had a
_51DEGREES_ form at all. TEST_IPI_RESOURCE_KEY, TEST_LICENSE_KEY and
LICENSEKEY stay, each being one name with nothing standing behind it.

Deletes the "set them with env" instruction from AGENTS.md, which
documented a way round a name this change fixes, and replaces it with
a plain export.

Adds ci/tests/environmentVariableNames.test.js, which reads every file
git tracks and fails both on any name that starts with the digit and on
any read of a bare RESOURCE_KEY or LICENSE_KEY, with matcher tests
covering both expressions. The fallback check matches only a read,
being process.env.NAME, process.env['NAME'] and $env:NAME, so the
placeholder, the longer names and prose naming the old name do not
trip it. Registers ci as a jest project and adds ci/.eslintrc so the
folder lints with the house rules.

* Read the IP Intelligence super key under its _51DEGREES_ name, and drop the test names nothing reads

The IP Intelligence test constants still named TEST_SUPER_RESOURCE_KEY,
which six IP Intelligence cloud tests try after the two shared names and
which CI no longer sets, so it was a fallback under an old name. It now
names _51DEGREES_RESOURCE_KEY_SUPER, as Device Detection does.

TEST_IPI_RESOURCE_KEY and TEST_LICENSE_KEY were exported by CI and
declared in the test constants, but no test reads either constant, so
both exports, the constants and their type declarations go.

The two Device Detection readmes listed the five TEST_*_RESOURCE_KEY
names for the SetHeader tests, which nothing reads now. They list the
_51DEGREES_RESOURCE_KEY_* names the tests read.

* Read the IP Intelligence getting-started key in dot notation

The full build failed npm run lint on the dot-notation rule at line 59,
because a name that starts with an underscore is a valid identifier, so
process.env._51DEGREES_RESOURCE_KEY is the form the house rules ask for.
This was the only literal bracket read of an environment variable in
the repository.

* Test that both name matchers catch a lower-case name

On Windows an environment variable name ignores letter case, so
process.env.resource_key there reads the same variable as
process.env.RESOURCE_KEY. A check that honours case is weaker than the
platform it guards, so each matcher gets a lower-case case. Both fail
until the expressions ignore case.

* Ignore letter case in both environment variable name matchers

Node's process.env documentation says that on Windows environment
variables are case-insensitive, so a lower-case read such as
process.env.resource_key reaches RESOURCE_KEY there. Both expressions
now take the i flag, so the guard is no weaker than the platform. The
repository has no lower-case offender, so both scans stay empty.

* Test that the fallback check matches PowerShell's braced read

* Match PowerShell's braced read in the fallback check

${env:NAME} reads the same variable as $env:NAME, so a bare RESOURCE_KEY or LICENSE_KEY read in that form now fails the check too.

* Build the braced PowerShell read in the test from parts, so lint does not take it for a template
* Test that the Cloud Request Engine sends evidence the cloud does not list

* Keep the evidence no element accepts, and send it to the cloud

* Wait for the Cloud Request Engine to be ready before adding evidence in the all evidence tests

Evidence added before the cloud's evidencekeys answer arrives passes the engine's default filter, so the test of evidence the cloud does not list passed without the fix. Waiting for ready gives the engine the filter a running service has, and the test then fails without the fix.

* Run the all evidence tests on a pipeline holding the Cloud Request Engine alone

The Set Headers Element accepts every evidence key and the JavaScript Builder every query and header key, so in a default pipeline nothing is filtered out and the tests passed without the fix. With both left out, the engine's own filter decides, and the tests fail without the fix. The recording client also answers the accessible properties post a license key makes.

* Test that the Cloud Request Engine takes the accepted evidence before the set aside evidence

* Take the accepted evidence first when building what the Cloud Request Engine sends

addQueryData keeps the first value it meets for a name, so the order of the sendable evidence decides which of two values with one name is sent. The accepted values now come first, and a set aside value never replaces an accepted one of the same key.

* Test that evidence named like an object member is sent to the cloud

* Look for a name among the Cloud Request Engine's own members only

sendableEvidence and addQueryData tested a name with in, which also finds the members every object has, so evidence named constructor, toString or valueOf was taken as already present and never sent. Both now ask whether the object has a member of its own by that name.

* Test that two spellings of one set aside evidence key are sent once

* Leave the reserved names to addQueryData's check

rc's addQueryData now leaves out Evidence whose name is resource, license, licence or values, so isSent drops its own copy of that check and leaves out the web request view alone. What reaches the cloud is unchanged.
…places (#62)

* Add tests for IP Intelligence latitude, longitude and WKT to 3 decimal places

* Give IP Intelligence latitude, longitude and WKT points to 3 decimal places

The Rounding of Azimuth and Declination values section of the value types
page says a location is given to 3 decimal places, as a number, as a string
and as a point in a WKT shape. The engine asked for 16 decimal places in WKT
and gave latitude and longitude as the native engine returned them.

* Say why the rounded properties are named rather than found by stored type

Go and Rust ask the C library which stored type a value came from and round
an Azimuth or a Declination. The swig results give no stored type, so the
two properties the IP Intelligence data model gives as those types are
named.
* Move cxx to 865cee97, the 51degrees-cxx rc commit of 9 November

* Generate the SWIG wrappers again for 51degrees-cxx 865cee97

Generated with SWIG 4.5.0 built from https://github.com/51Degrees/swig.git
at refs/heads/node-24-holder-this, commit
48753552ee7d0e0f886f9d624f761a501ce70b64, using the commands in the README
with cxx at 865cee97432d8b7b047223b82c64fc31a489735f. The Device Detection
wrapper is unchanged. The IP Intelligence wrapper no longer gives the
caller ownership of the collection configuration that the getters return.

* Move cxx to the 51degrees-cxx rc commit b4f769ad

Takes the eight 51degrees-cxx pull requests merged into rc after 865cee97, among them the rename of src/common/Date.cpp to date_pp.cpp so it no longer clashes with date.c on a case-insensitive file system.

* Move cxx to the 51degrees-cxx rc commit cfad8978

Takes 51degrees-cxx #130, which writes the text of each IP Intelligence value with its weight in C, and #146, which gives a reason and a message when a property has no value in the matched results.

* Move cxx to the 51degrees-cxx release commit 7c4ac436
…ds (#45)

* Test the caching headers of the client-side endpoints

The tests ask for the Cache-Control, Vary and weak ETag headers of the
web integration specification's cache header detail on both client-side
endpoints, the same ETag for the same evidence in two processes, a 304
for a matching If-None-Match on a GET and a 412 on a POST, as RFC 9110
section 13.1.2 requires, and private, no-cache with no ETag on a
response to a request that recorded errors, while a matching
If-None-Match still gets its 304.

* Send the client-side caching headers, and one ETag for the same evidence

Node's client-side JSON and JavaScript endpoints are written in each web
example, and none sent Cache-Control, Vary or an ETag, or answered
If-None-Match, so they had none of the web integration specification's
cache header detail.

Helpers.writeClientSideResponse writes either endpoint's response with
those headers. The ETag is a SHA-256 of the evidence the request
carries, read from the request again, so evidence processing adds, such
as the new session id the sequence element makes, takes no part, and
leaving out the request's precondition and cache headers, so that a
revalidation can match. The same evidence therefore gives the same ETag
in every process. It is weak, as RFC 9110 section 8.8.3 asks of a
validator that can stay the same when the response changes. A matching
If-None-Match gets a 304 with the same Cache-Control, Vary and ETag to a
GET or HEAD, and a 412 to any other method, as RFC 9110 sections 13.1.2
and 15.4.5 require. A response to a request whose processing recorded
errors carries Cache-Control private, no-cache and no ETag, and a
matching If-None-Match still gets its 304.

The web examples of Device Detection, IP Intelligence, location and the
pipeline's custom element example use it for every client-side endpoint.

* Regenerate the pipeline core types for writeClientSideResponse

* Test that the client-side ETag does not read the request after processing

A browser can stop waiting while the pipeline processes, and the
request's socket then has no addresses. Reading the request again for
the ETag then threw a TypeError, which stopped the Device Detection
cloud web example during the Selenium contract tests.

* Build the client-side ETag from what was read from the request

writeClientSideResponse read the request again for its ETag after
processing. A browser can stop waiting while the pipeline processes,
and the request's socket then has no addresses, so the read threw a
TypeError. In the Selenium contract tests on macOS this stopped the
Device Detection cloud web example.

addFromRequest now keeps every value it reads, under its evidence key,
in requestEvidence, and the ETag is built from that, hashing only the
evidence an element accepts, so it no longer touches the request.
Evidence added by hand has no such record, so its ETag is built from
the flow data's evidence.

* Test that usage sharing accepts no evidence and shares the web request

The tests check that a pipeline holding the usage sharing element does
not accept If-None-Match or an arbitrary header, that the web request is
in the evidence as server.http-request, that a header no other element
accepts is still shared from it while the Cookie header and a blocked
header are not, that evidence added by hand is shared as before, and
that the client-side endpoint answers a revalidation with a 304 and
leaves evidence only usage sharing reads out of its ETag.

* Accept no evidence in usage sharing, and share the web request it finds

The usage sharing element's evidence key filter accepted every header
but Cookie, and the evidence an element accepts is what caching, the
ETag and the Vary header are built from, so the client-side ETag took in
every header the usage sharing element collected, such as the client's
51D_ cookies and the server's own address.

The element now accepts no evidence. It shares what it finds, being
every value of the web request and every evidence value, with the
evidence value winning for a key in both, under the same rules as
before, and the tracker compares the values that would be shared.

Evidence keeps a key an element accepts, as before, or one an element
keeps without accepting it, given by the new keptEvidenceKeyFilter. The
usage sharing element keeps what it would share, so evidence added by
hand is shared as it was. addFromRequest also adds the web request as
server.http-request, as a frozen object holding every value it read
under its evidence key, whether or not an element accepts it. get
returns it and getAll leaves it out, because it is the request rather
than a value to process. writeClientSideResponse hashes only the
evidence an element accepts.

* Test the web request in the cloud request engine, and the sequence element's keys

The tests check that the cloud request engine neither sends
server.http-request to the cloud service nor lets it change its cache
key, and that the sequence element accepts query.session-id and
query.sequence itself, so that alone in a pipeline it keeps a session
going.

* Accept the session id and sequence in the sequence element

The sequence element read and wrote query.session-id and query.sequence
but accepted no evidence, so the flow data kept them only because the
JavaScript builder or the usage sharing element accepted them. Alone in
a pipeline, the session id it made was dropped. It now accepts both, as
its specification lists them.

The cloud request engine and the on-premise engines read getAll(),
which leaves server.http-request out, so neither sends it to the cloud
service or to native code, and it takes no part in the cloud request
engine's cache key. The tests in the commit before check the cloud
request engine.

* Test that the default pipeline accepts only the evidence its elements read

The JavaScript builder accepted every header and the set headers element,
having no filter of its own, accepted every key, so the default pipeline
accepted If-None-Match and any other header. The tests ask that it does
not, and that it still accepts the host, the protocol and the query
values the JavaScript builder reads, and the User-Agent an engine reads.

* Accept only the evidence they read in the JavaScript builder and set headers

Under the rule that an element accepts only the evidence that can
change its results, two elements of the default pipeline were wide. The
JavaScript builder accepted every header, though only the host and the
protocol change the script, and the set headers element had no filter
of its own, so it accepted every key, though it reads only the other
elements' results. With either in the pipeline, If-None-Match and every
other header were accepted evidence.

The JavaScript builder now accepts every query value, which the script
sends back as its parameters, and header.host and header.protocol. The
set headers element accepts none. The usage sharing element keeps the
headers it shares, so what it shares is unchanged.

* Test that an element which declares no evidence accepts none

* Accept no evidence in an element that declares none, as the specification asks

* Test that the JavaScript builder and the sequence element accept only what they read, and keep the evidence the tests read with elements that say so

* Accept only what the JavaScript builder and the sequence element read

* Test that evidence set before the cloud service's keys arrive is kept

* Keep every key in the cloud request engine until the cloud service's keys arrive

* Remove the doc comment the JavaScript builder's filter replaced

* Restore the usage sharing files' line endings changed in the previous commit

* Test the request view's method, path and session, and that the Cookie header is never shared

* Never share the Cookie header, and give the request view its method, path and session values

* Wrap the never shared comment to 80 columns

* Keep every key in the on-premise engines until the data file arrives

An on-premise engine reads the keys it accepts from the data file when it
initialises, which waits for the download when updateOnStart is set and
there is no file yet. With an element now accepting nothing by default,
evidence added before the file arrived was dropped, so processing failed
with 'The evidence required to determine this property was not supplied'.
Both engines now keep every key until initEngine replaces the filter with
the keys the file lists, as the cloud request engine does for the keys the
service lists.

* Take the JavaScript builder's case test to a key it accepts

The builder now accepts only what it reads, which the specification's
Accepted Evidence list for the element gives as header.host,
header.protocol and the query string. The test's point is that a key is
found whatever its letters, so it uses the host rather than the user agent,
which the builder does not read.

* Test that the JavaScript builder accepts the four keys the page lists

* Accept the JavaScript builder's four keys, and read its parameters from the evidence

* Keep the JavaScript builder's query string evidence as it was

* Say which elements accept every key in the ETag comment

* Test the request line on the view and one value for a key in both places

* Give the view the whole request line, and fold a key found in both places
…ss (#73)

* Test that a Cloud Request Engine with no cloud engine serves a repeated request from its cache

Only a cloud engine calls ready, so a Cloud Request Engine on its own never fetches its evidence keys, builds its cache key from every Evidence value, and with the Sequence Element first misses the cache on every request. This test fails until the engine fetches the keys itself.

* Fetch the Cloud Request Engine's evidence keys before its first process

The cache key is built from the engine's evidence key filter, which stayed the filter that accepts every key until something called ready. Only a cloud engine does, so a Cloud Request Engine with a cache and no cloud engine keyed its cache on every Evidence value, and with the Sequence Element first missed on every request. process now waits for the evidence keys and properties first, sharing one fetch between the requests that arrive while it runs, and a failed fetch is tried again on the next process.

* Give the update on start-up tests time to download a whole data file

Each test serves, downloads and unpacks the Lite data file, and on Windows with Node 22 one of them has twice gone past the 10 second test timeout, failing full builds that changed nothing near it. The two files now allow 60 seconds a test.

* Give processInternal its own comment back and put process after it

* Answer the accessible properties post in the reserved Evidence names test

The engine now fetches its properties and evidence keys before its first process, and under a license key the properties request is a post, so the test's post recorder saw two posts. It answers that one with the mock's properties and records only the request it reads.

* Test that the Cloud Request Engine fetches its evidence keys and properties once

* Fetch the Cloud Request Engine's evidence keys and properties once, not for every process
…one name once whatever its letter case (#75)

* Test that the Cloud Request Engine ranks Evidence prefixes in evidence.md's order

* Rank server, fiftyone and location Evidence in evidence.md's order, then other prefixes alphabetically, ignoring letter case

* Test that one Evidence name in two letter cases is sent once, from the higher prefix

* Send one Evidence name once, whatever its letter case, by precedence

* Test that the cloud request content reads the names already added once

* Find a name already added in the cloud request content with one read
* Test the cloud endpoint variable's one name

The cloud request engine and the 51Did client must read their endpoint from
_51DEGREES_CLOUD_ENDPOINT and not from the old name, and the environment
variable name check now fails on any file that still names the old one.

* Read the cloud endpoint from _51DEGREES_CLOUD_ENDPOINT

The cloud request engine and the 51Did client read their endpoint from
_51DEGREES_CLOUD_ENDPOINT, the name the other 51Degrees languages use, with
no fallback to the old name. The examples, readmes, type declarations,
integration tests and the CI script name the new variable.
* Use the standard licence text once, with its additional terms in NOTICE

The root LICENSE and copies in device-detection, ip-intelligence,
location and pipeline held the 51Degrees header form, which GitHub
cannot match to a licence. The root LICENSE now holds the EUPL v1.2 text
byte for byte as 51degrees-cxx has it, NOTICE holds the two additional
terms as 51degrees-cxx has them, and the four other copies are removed.
Every package.json declares EUPL-1.2, which is where npm reads the
licence from.

* Keep the additional terms in the file headers rather than in NOTICE

The two additional terms apply where each file's header states them, so
the repository has LICENSE alone, without a NOTICE file. The README's
licence line goes back to naming LICENSE only.
… clock (#82)

* Hold the clock the back-off reads where a test processes requests during a pause

The usage sharing back-off tests that process requests while sending is
paused needed the runner to finish processing inside the pause, so a
slow runner let the pause end and the element queue or send evidence the
test expected it to drop. They now hold the clock the back-off reads and
move it past the end of the pause themselves, and wait on the queue
reaching its limit and on the element's own signals rather than on fixed
sleeps.

* Give each test its own receiver, and leave no element with data to send

A test's usage sharing element can outlive the test that made it, with a
pause still running and entries still queued. Every test listened on the
same port, so those entries reached the receiver of a later test and were
counted as its own.

* Say what each check in the drains test shows

The comment on the count of sends claimed nothing is sent for as long as
the pause lasts, but the check runs as soon as the five requests are
processed, so by itself it shows only that no send had been made by
then. The timing check after the pause is what catches a pause that was
not honoured, and it now says so.
…test on paired rounds (#84)

* Decide the dictionary read timing test on fresh pairs timed side by side

* Count the case work instead of timing it, and settle the timing on spread

The timing test failed macOS by 0.18 per cent. A read that finds its key
as given returns before letter case is looked at, so the two sides of the
comparison run the same code and the ratio they produce is noise. The
exact guarantee now comes from a test that counts the reads which had to
look past letter case, which has no clock in it and cannot be flaky. The
timing test stays as a backstop for a slowdown the count cannot see, with
its shape and its bound taken from the measured spread on macOS, which
leaves it tighter than the tenth it allowed before.

* Give the timing test its own timeout, and name the bound as a ratio

A runner busy enough to need the second measurement is also one that takes
longer over both, so the file's ten seconds is not enough headroom for a
test that can measure twice.

* Hold the case lookup count outside the dictionary

Element Data hands back a proxy whose getter answers an unknown field by
reading a property of that name, so a field holding zero is falsy and the
read throws "Property caseLookups not found". The count lives in the test
instead. Caught by Ubuntu_ARM_Node_24 in run 43150984588.

* Say what the bound is set from in the timing test comment
Each test job downloaded the Asn data file again with the data repository's script, outside the asset cache that the run fills once and every test job restores. They are now fetched as assets with the others, restored from the cache in each test job, and copied or linked to where the tests and examples look for them, so no test job downloads a data file. The notes say CI takes every data file from the assets, and that a new data file needs an entry in common-ci's steps/fetch-assets.ps1.
* Test that the old cloud endpoint variable name is still read

The cloud request engine, the 51Did client and the creator context
example must read _51DEGREES_CLOUD_ENDPOINT first and, when that is unset
or empty, FOD_CLOUD_API_URL, reporting once that the old name is
deprecated. These tests say so and fail until the old name is read again.

The environment variable name check now allows the old name in the files
that will read or document it, and fails while a listed file does not
name it.

* Read the old cloud endpoint variable name behind the new one

The rename of the cloud endpoint variable to _51DEGREES_CLOUD_ENDPOINT
stopped reading FOD_CLOUD_API_URL, which the last release read. A patch
release must not drop a name a caller may have set, so the old name is
read again for this release, deprecated.

The cloud request engine and the 51Did client read
_51DEGREES_CLOUD_ENDPOINT first. When that is unset or empty they read
FOD_CLOUD_API_URL and say once that it is deprecated. The engine logs a
warn message to its Pipeline on the first process, because it has no
Pipeline when it is built. The 51Did package has no logger, so the client
raises one Node DeprecationWarning for the process.

The creator context example no longer reads the variable itself. It
builds its client with no endpoint and gives the page the base the client
settled on, so it follows the same rule.

Neither package held the variable name in a public constant, so there is
no constant to restore.

* Correct three comments on the endpoint variable tests

The variable name check said every variable has one name with nothing
behind it, which is no longer true of the cloud endpoint, so the comment
now speaks of the resource key and the licence key alone. The title of
its old name test said the name appears only where it is still read, when
four of the six listed files word or document it without reading it.

The header of the creator context server test described only the /redeem
route. It now also says the endpoint tests start the example as its own
process and ask for its page over the loopback address.

No code or test behaviour changes.

* Test that every old environment variable name is still read

Tests, test helpers and examples read their keys and data file paths
from names such as RESOURCE_KEY, LICENSE_KEY, the TEST_ key names and
names starting with the digit, until those names were aligned on the
_51DEGREES_ prefix. Every such name is public, so each must still be
read, deprecated, when the name that replaced it is unset or empty.

The new tests set only the old name and check that the value is used and
that the deprecation is printed once, naming the new name. They cover the
key helpers of the pipeline, device detection and IP Intelligence tests,
the example helpers, the constants that held an old name and each example
that reads a variable itself. They fail until the old names are read
again.

The environment variable name check now allows a digit-initial name and
a read of a bare key name in the files that will read them as fallbacks,
and fails while a listed file does not carry one.

* Read every old environment variable name behind the name that replaced it

Every environment variable name that a package, a test, an example or a
CI script reads is a public name, and a patch release never removes or
renames one. The alignment on the _51DEGREES_ prefix dropped these names,
which the last release read:

- RESOURCE_KEY, behind _51DEGREES_RESOURCE_KEY
- LICENSE_KEY, behind _51DEGREES_LICENSE_KEY
- 51DEGREES_RESOURCE_KEY, 51DEGREES_DD_PATH and 51DEGREES_IPI_PATH,
  behind the same names with a leading underscore
- TEST_SUPER_RESOURCE_KEY, TEST_PLATFORM_RESOURCE_KEY,
  TEST_HARDWARE_RESOURCE_KEY, TEST_BROWSER_RESOURCE_KEY and
  TEST_NO_SETHEADER_RESOURCE_KEY, behind the _51DEGREES_RESOURCE_KEY_
  names

Each is read again, only when the name that replaced it is unset or
empty, and the first read that finds a value there prints once that the
old name is deprecated and which name replaces it. The test key helpers
and the example helpers do this in one place each, and the examples and
tests that read a variable themselves do it where they read.

The constants that held an old name are back under their old names with
their old values, marked deprecated: legacyEnvVars and licenseKeyEnvVar
in the device detection test constants, ipiResourceKeyEnvVar and
licenseKeyEnvVar in the IP Intelligence test constants, and
LEGACY_RESOURCE_KEY_ENV_VAR and LEGACY_DATA_FILE_PATH_ENV_VAR in the
example helpers.

The messages that tell a reader which variable to set still name only
the _51DEGREES_ names.

* Test that constants keep the old variable name they held

Five exported constants kept their names when the variable they name was
renamed, and now hold the new name. Code that passes one of them to the
environment would no longer read the variable it read before, so each
must hold its old value again, with a new constant for the name in use.

These tests say so for superResourceKeyEnvVar in the IP Intelligence test
constants, RESOURCE_KEY_ENV_VAR in the IP Intelligence cloud example
helper, DATA_FILE_PATH_ENV_VAR in the device detection on-premise example
helper, and IPI_DATA_FILE_ENV_VAR and DD_DATA_FILE_ENV_VAR in the IP
Intelligence on-premise example helper. They fail until the values are
restored.

* Give five constants back the old variable name they held

Five exported constants kept their names when the variable they name was
renamed, and held the new name. Code that passes one of them to the
environment would no longer read the variable it read before, so each
holds its old value again, marked deprecated, and a new constant beside
it holds the name in use:

- superResourceKeyEnvVar in the IP Intelligence test constants is
  TEST_SUPER_RESOURCE_KEY again, with superResourceKeyVariable
- RESOURCE_KEY_ENV_VAR in the IP Intelligence cloud example helper is
  51DEGREES_RESOURCE_KEY again, with RESOURCE_KEY_VARIABLE
- DATA_FILE_PATH_ENV_VAR in the device detection on-premise example
  helper is 51DEGREES_DD_PATH again, with DATA_FILE_PATH_VARIABLE
- IPI_DATA_FILE_ENV_VAR and DD_DATA_FILE_ENV_VAR in the IP Intelligence
  on-premise example helper are 51DEGREES_IPI_PATH and 51DEGREES_DD_PATH
  again, with IPI_DATA_FILE_VARIABLE and DD_DATA_FILE_VARIABLE

The examples and tests that used those constants to read a variable, or
to tell a reader which variable to set, use the new constants, so what
is read first and what is printed do not change.
…ery detection takes (#85)

* Count the masking a detection does, and pin where the update service masks

* Mask license keys in the update service, not on the pipeline's log

* Keep the mask on the console branch, where no detection goes

* Say only what is true about which messages reach the console branch

* Carry the log documentation into the generated types

* Say that the cloud engine's errors also reach the console branch of Pipeline.log
* Test that the file system watcher runs on its own setting

The watcher test now expects a watcher with automatic updates on and
off, and none with the watcher's own setting off. It replaces the test
that no watcher is created while autoUpdate is off.

The watched data files in dataFileUpdateService.test.js and the test
that the watcher is logged were given autoUpdate, a polling interval
and fake timers only to get a watcher, so they lose them again.

* Run the file system watcher on its own setting

registerDataFile creates the watcher when fileSystemWatcher is set,
whatever autoUpdate is, because watching a file downloads nothing. A
data file with automatic updates off, such as one with no License Key,
is watched again, as it is on main.
Automation51D and others added 2 commits November 13, 2026 04:12
Co-authored-by: Automation51D <51DCI@51Degrees.com>
* Test that a key filter puts its list in lower case once

A detection checks each piece of evidence against an engine's key filter
as the evidence is added, and again as the engine reads the evidence it
accepts. BasicListEvidenceKeyFilter puts every key in its list in lower
case on each check, so each detection pays for the whole list at least
twice. These tests count those calls, and check that letter case, an
empty list, and a list changed or replaced after a check behave as
before.

* Put a key filter's list in lower case once, not on every check

BasicListEvidenceKeyFilter keeps its keys in lower case in a set, made
from the list on the first check and again whenever the list is replaced
or changes, so a check puts only the key being checked in lower case.
Which keys are kept is unchanged, and the device detection and IP
Intelligence engines still pass the native engine only the evidence it
reads.

* Match the lint and the generated type declaration

One blank line after the licence header, and the private member marker
where tsc writes it, after list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants