Repository navigation
Release candidate - #83
Draft
jwrosewell wants to merge 61 commits into
Draft
jwrosewell wants to merge 61 commits into
jwrosewell wants to merge 61 commits into
Conversation
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>
…ch to releases in AGENTS.md'
…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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Releases what has collected on
rcsince 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
performanceProfilenow defaults to'Balanced', where it was'LowMemory', andupdateMatchedUserAgentis now on. SetperformanceProfile: 'LowMemory'andupdateMatchedUserAgent: falseto keep the old behavior (#42).PipelineBuildernow adds the Sequence Element first, so only a Pipeline built withaddSequenceElement: false, or one that addsJSONBundlerElementby hand, is affected (#53, #74).sequenceentry, which holdssession-idandsequence. A request with a session id and no sequence now gets sequence 1, where it got 2 (#59).resource,license,licenceandvalues, 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).51D_, a parameter only when its name starts51D_or is inqueryWhitelist, and never theCookieheader. SetshareAll: trueto share as before (#35, #45).evidenceKeyFilteraccepted every key and now accepts none, andflowData.evidence.getgivesundefinedfor a key that no element accepts. Give the elementevidenceKeyFilter: new BasicListEvidenceKeyFilter([...])with the keys it reads (#45).Error during check for data update, so a log search or alert on the old text needs to change (#69).Areas, so18.542741172521133is now18.543(#62).1.2.3.4/24,1.2.3.4, 5.6.7.8and01.2.3.4were read and are now skipped. Pass one address in each value (#67).propertieskeeps 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).protocolother thanhttporhttps, anobjNamesuch aswindowordocument(#31, #60), and a Translation Enginebehaviorthat names none ofOriginal,EmptyStringorFlowError(#49).INVALID_BASE64.FodId.toStandardBase64returnsnullfor such a text, so a TypeScript caller needs to handlenull(#40, #56).query,header,cookie,server,fiftyone,location, and compares prefixes and names ignoring letter case, soheader.User-Agentandquery.user-agentare sent as one field (#75)._51DEGREES_CLOUD_ENDPOINTreplacesFOD_CLOUD_API_URL, which still works in this release. The Cloud Request Engine and the 51Did client read_51DEGREES_CLOUD_ENDPOINT, and readFOD_CLOUD_API_URLonly 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 NodeDeprecationWarningfor the process. ThebaseURLandendpointoptions still come first. Set_51DEGREES_CLOUD_ENDPOINTto the same value as the old name (#77, #88).What changes
Web integration and client-side caching
Helpers.writeClientSideResponsewrites the response of a client-side JavaScript or JSON endpoint withCache-Control,Varyand an ETag. Node.js has no web integration package, so call it from your own endpoints, as the web examples do (#44).W/"...", andIf-None-Matchis 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 hasCache-Control: private, no-cacheand no ETag. The same Evidence gives the same ETag in every process (#44).Evidence.addFromRequestadds the web request as a read-only view underserver.http-request(#45).JavaScript Builder
httporhttps, 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
header.User-Agentandheader.user-agentare one key, andflowData.device.get('IsMobile')works in any letter case (#71, #68).Cloud
licenseKey. With a License Key alone the Properties wanted must be named withrequestedProperties. Both keys now travel in the request body (#64).*. The packages mask a key where one can enter a message, so the mask no longer runs on each detection, andPipeline.logpasses the message it is given to its listeners unchanged (#58, #85).cacheSizesets (#57).Usage Sharing
trackSessionoption tells sessions apart (#36, #35).escaped="true", where it wasreplaced="true", and cuts a value at 10,000 code points, where it was 512 characters (#54).Data updates
updateCompletelistener that throws is logged (#69).updateOnStartand a License Key or an update URL, an on-premise Engine downloads a missing Data File, where the build threw.DataFileUpdateService.updateFromMemoryis new (#52).fileSystemWatchersetting, whether automatic updates are on or not, and needs no License Key, as in the published packages (#52, #90).Device Detection and IP Intelligence
IsMobileon Ubuntu ARM with Node.js 22 (run 42995016804, #68).Translation
TranslationEngineaccepts the specification's option names,sourceElementDataKeyandmissingTranslationBehavior.sourceDataKeyandbehaviorstill work and are deprecated (#49).C library
51D_GetHighEntropyValuesand51D_StructuredUserAgentthe query value is used before the cookie value (#67, #55, #92).Examples and documentation
LICENSEfile at its root, the EUPL 1.2 text, and every JavaScript source file carries the standard 51Degrees header. Thefiftyone.geolocationpackage no longer carries aLICENSEfile 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 aDeprecationWarningin 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).FOD_CLOUD_API_URL_51DEGREES_CLOUD_ENDPOINT51DEGREES_DD_PATH_51DEGREES_DD_PATH51DEGREES_IPI_PATH_51DEGREES_IPI_PATH51DEGREES_RESOURCE_KEY,RESOURCE_KEY_51DEGREES_RESOURCE_KEYLICENSE_KEY_51DEGREES_LICENSE_KEYTEST_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_SETHEADERFixed
TypeError: Cannot convert undefined or null to objectwhen the Resource Key did not include its product. One error is now logged for each Engine and the request carries on (#41).updateCompletelistener that threw ended the Node.js process (#65).<Device>elements of a message, logged a failed send asUsage data sent, and wrote<ClientIP>undefined</ClientIP>for a request with no client IP (#35, #36, #54).RangeError: Maximum call stack size exceededwhile 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.Setting 'PullRequestSha'lines, being Configure and the eight Build and Test jobs, read8f14aa0459552aad9187539448bcc1e0b94dfce1, which is this pull request's head and the head ofrc.macOS_Node_22andmacOS_Node_24both ran and 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.
Ubuntu_ARM_Node_24measured 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 onUbuntu_ARM_Node_24, at 30,352 against a floor of 30,510. #91 timedmainagainstrcon one ARM runner, foundrcslower 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 intorcat 04:50 UTC on 13 November 2026.Against
main. Run 43355023653, from #91, timedmain,rcbefore #91 and #91's change on oneubuntu-22.04-armrunner, over nine rounds with the order turning. The medians were 33,711 detections a second formain, 30,437 forrcbefore #91 and 34,869 with #91, which measured higher thanmainin 7 of the 9 rounds. Apart from #91 itself,rchas 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 aLICENSEfile and a test. So this release is not slower thanmainon that runner.Between
13ad4ebband20868944,rctook #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
rcare later work and not in this release, being #89, #87, #66, #63, #46, #37, #27, #15 and #10.Only the patch number moves.