@lowdefy/helpers 6.2.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/appStorageKey.js +20 -0
- package/dist/buildPagePath.js +39 -0
- package/dist/builtinMessages.js +7 -0
- package/dist/callerHash.js +28 -0
- package/dist/callerStamp.js +59 -0
- package/dist/cleanBuildArtifact.js +24 -0
- package/dist/createTraceId.js +35 -0
- package/dist/filterElementsChain.js +68 -0
- package/dist/findInteractiveControls.js +25 -0
- package/dist/getSafeHref.js +49 -0
- package/dist/index.js +24 -1
- package/dist/isChainHeaderEnd.js +33 -0
- package/dist/isInteractiveControl.js +22 -0
- package/dist/isMountEventName.js +27 -0
- package/dist/isTraceId.js +21 -0
- package/dist/journeyTargetSelectors.js +103 -0
- package/dist/listHash.js +25 -0
- package/dist/normalizeCaller.js +30 -0
- package/dist/pageInstanceKey.js +36 -0
- package/dist/pairTraceEvents.js +102 -0
- package/dist/parseCallerStamp.js +46 -0
- package/dist/parseElementsChain.js +58 -0
- package/dist/parsePageId.js +23 -0
- package/dist/parsePathPattern.js +88 -0
- package/dist/parseRowIndex.js +24 -0
- package/dist/projectCaughtError.js +6 -2
- package/dist/readChainValue.js +42 -0
- package/dist/rolesHash.js +34 -0
- package/dist/serializer.js +3 -2
- package/dist/sha256Hex.js +170 -0
- package/dist/stampHash.js +24 -0
- package/dist/targetFromElementsChain.js +103 -0
- package/dist/traceIdDate.js +23 -0
- package/package.json +5 -3
package/dist/listHash.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import stampHash from './stampHash.js';
|
|
16
|
+
// The stamp hash of a list whose order carries no meaning (the organization ids
|
|
17
|
+
// a person belongs to): sorted first, so the same set always hashes the same,
|
|
18
|
+
// and JSON-encoded, so no separator inside an entry can make two lists read as
|
|
19
|
+
// one.
|
|
20
|
+
function listHash({ values }) {
|
|
21
|
+
return stampHash(JSON.stringify([
|
|
22
|
+
...values
|
|
23
|
+
].sort()));
|
|
24
|
+
}
|
|
25
|
+
export default listHash;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { snakeCase } from 'change-case';
|
|
16
|
+
import type from './type.js';
|
|
17
|
+
// Shallow snake_cases every own top-level key of the resolved caller - the
|
|
18
|
+
// object mirror of the adapter's schema-wide fieldName derive. Values are
|
|
19
|
+
// copied by reference: the attributes and profile bags (and the roles array)
|
|
20
|
+
// ride through whole, so their inner keys - app/module-owned, not platform
|
|
21
|
+
// surface - are never transformed.
|
|
22
|
+
function normalizeCaller(user) {
|
|
23
|
+
if (type.isNone(user)) return user;
|
|
24
|
+
const normalized = {};
|
|
25
|
+
for (const [key, value] of Object.entries(user)){
|
|
26
|
+
normalized[snakeCase(key)] = value;
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
export default normalizeCaller;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import buildPagePath from './buildPagePath.js';
|
|
16
|
+
import parsePathPattern from './parsePathPattern.js';
|
|
17
|
+
import type from './type.js';
|
|
18
|
+
// The key a page instance's context and input are stored under. A page with placeholders has one
|
|
19
|
+
// instance per set of decoded values, keyed by the path the builder writes for them, so every
|
|
20
|
+
// spelling of the same values in an arriving URL gives one key. "#" cannot appear in a page id and
|
|
21
|
+
// the builder encodes it in values, so no instance key equals another page's key.
|
|
22
|
+
function pageInstanceKey({ pageId, path, pathParams }) {
|
|
23
|
+
if (type.isUndefined(path)) {
|
|
24
|
+
return `page:${pageId}`;
|
|
25
|
+
}
|
|
26
|
+
const hasPlaceholders = parsePathPattern(path).some((segment)=>type.isString(segment.name));
|
|
27
|
+
if (!hasPlaceholders) {
|
|
28
|
+
return `page:${pageId}`;
|
|
29
|
+
}
|
|
30
|
+
return `page:${pageId}#${buildPagePath({
|
|
31
|
+
pageId,
|
|
32
|
+
path,
|
|
33
|
+
pathParams
|
|
34
|
+
})}`;
|
|
35
|
+
}
|
|
36
|
+
export default pageInstanceKey;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import isMountEventName from './isMountEventName.js';
|
|
16
|
+
import type from './type.js';
|
|
17
|
+
// Absorbs event-loop and render delay between the DOM interaction and the
|
|
18
|
+
// moment the engine starts the event's actions, without reaching back to an
|
|
19
|
+
// unrelated earlier click.
|
|
20
|
+
const PAIRING_SLACK_MS = 1000;
|
|
21
|
+
function canBeCaused({ event }) {
|
|
22
|
+
return event.scope !== 'app' && !isMountEventName({
|
|
23
|
+
eventName: event.eventName
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// An interaction can have caused an event when the event's block encloses the
|
|
27
|
+
// interaction's target (the handler is on the clicked block or an ancestor of
|
|
28
|
+
// it) and the event started no later than its debounce plus the slack after
|
|
29
|
+
// the interaction.
|
|
30
|
+
function couldCause({ event, interaction }) {
|
|
31
|
+
if (!interaction.blockIds.includes(event.blockId)) return false;
|
|
32
|
+
const delay = event.startTimestamp - interaction.t;
|
|
33
|
+
const windowMs = (event.debounceMs ?? 0) + PAIRING_SLACK_MS;
|
|
34
|
+
return delay >= 0 && delay <= windowMs;
|
|
35
|
+
}
|
|
36
|
+
function findCause({ event, interactions }) {
|
|
37
|
+
let cause;
|
|
38
|
+
interactions.forEach((interaction)=>{
|
|
39
|
+
if (!couldCause({
|
|
40
|
+
event,
|
|
41
|
+
interaction
|
|
42
|
+
})) return;
|
|
43
|
+
if (type.isUndefined(cause) || interaction.t >= cause.t) {
|
|
44
|
+
cause = interaction;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return cause;
|
|
48
|
+
}
|
|
49
|
+
// Decides which DOM interaction caused which engine event, so a trace record
|
|
50
|
+
// means the same thing whichever source wrote it: the dev recorder runs this in
|
|
51
|
+
// the browser, the production pull in Node.
|
|
52
|
+
//
|
|
53
|
+
// - interactions: [{ id, t, blockIds }], `t` epoch ms, `blockIds` innermost first.
|
|
54
|
+
// - events: [{ id, blockId, eventName, startTimestamp, debounceMs, scope }].
|
|
55
|
+
//
|
|
56
|
+
// Mount-class and app events are never paired. Any other event pairs with the
|
|
57
|
+
// latest interaction that could have caused it; an event with none is
|
|
58
|
+
// programmatic and goes to `unpaired`. Within a pair, events on the innermost
|
|
59
|
+
// block come first, then the earliest, so the record builder takes the first
|
|
60
|
+
// as `event` and the rest as `also`.
|
|
61
|
+
function pairTraceEvents({ interactions, events }) {
|
|
62
|
+
const byInteraction = new Map();
|
|
63
|
+
const unpaired = [];
|
|
64
|
+
events.forEach((event, order)=>{
|
|
65
|
+
const cause = canBeCaused({
|
|
66
|
+
event
|
|
67
|
+
}) ? findCause({
|
|
68
|
+
event,
|
|
69
|
+
interactions
|
|
70
|
+
}) : undefined;
|
|
71
|
+
if (type.isUndefined(cause)) {
|
|
72
|
+
unpaired.push(event.id);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!byInteraction.has(cause)) byInteraction.set(cause, []);
|
|
76
|
+
byInteraction.get(cause).push({
|
|
77
|
+
depth: cause.blockIds.indexOf(event.blockId),
|
|
78
|
+
event,
|
|
79
|
+
order
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
const pairs = interactions.filter((interaction)=>byInteraction.has(interaction)).map((interaction)=>{
|
|
83
|
+
const paired = [
|
|
84
|
+
...byInteraction.get(interaction)
|
|
85
|
+
].sort((a, b)=>{
|
|
86
|
+
if (a.depth !== b.depth) return a.depth - b.depth;
|
|
87
|
+
if (a.event.startTimestamp !== b.event.startTimestamp) {
|
|
88
|
+
return a.event.startTimestamp - b.event.startTimestamp;
|
|
89
|
+
}
|
|
90
|
+
return a.order - b.order;
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
interactionId: interaction.id,
|
|
94
|
+
eventIds: paired.map((entry)=>entry.event.id)
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
pairs,
|
|
99
|
+
unpaired
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export default pairTraceEvents;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import type from './type.js';
|
|
16
|
+
const FIELDS = [
|
|
17
|
+
'session',
|
|
18
|
+
'roles',
|
|
19
|
+
'memberships'
|
|
20
|
+
];
|
|
21
|
+
// Reads an x-lowdefy-caller header (see callerStamp) into
|
|
22
|
+
// { caller, session, roles, memberships }. A field the header does not carry is
|
|
23
|
+
// null; an anonymous caller is { caller: '' } with every field null. No header
|
|
24
|
+
// is null: the response was not answered by a route that resolved a caller (it
|
|
25
|
+
// failed before, or came from a proxy), so it claims no caller.
|
|
26
|
+
function parseCallerStamp(header) {
|
|
27
|
+
if (type.isNone(header)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const [caller, ...fields] = header.split(';').map((part)=>part.trim());
|
|
31
|
+
const stamp = {
|
|
32
|
+
caller,
|
|
33
|
+
session: null,
|
|
34
|
+
roles: null,
|
|
35
|
+
memberships: null
|
|
36
|
+
};
|
|
37
|
+
fields.forEach((field)=>{
|
|
38
|
+
const separator = field.indexOf('=');
|
|
39
|
+
const name = field.slice(0, separator);
|
|
40
|
+
if (separator > 0 && FIELDS.includes(name)) {
|
|
41
|
+
stamp[name] = field.slice(separator + 1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return stamp;
|
|
45
|
+
}
|
|
46
|
+
export default parseCallerStamp;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import isChainHeaderEnd from './isChainHeaderEnd.js';
|
|
16
|
+
import readChainValue from './readChainValue.js';
|
|
17
|
+
function parseHeader(header) {
|
|
18
|
+
const [tag, ...classes] = header.split('.');
|
|
19
|
+
return {
|
|
20
|
+
tag: tag.toLowerCase(),
|
|
21
|
+
classes: classes.filter((name)=>name !== '')
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Splits a posthog-js `$elements_chain` into entries, target first. Each entry is
|
|
25
|
+
// `tag.class1.class2:` followed by sorted `key="value"` pairs, and entries are joined by `;`.
|
|
26
|
+
// Total: a malformed chain gives the entries read before the fault.
|
|
27
|
+
function parseElementsChain(chain) {
|
|
28
|
+
const entries = [];
|
|
29
|
+
let index = 0;
|
|
30
|
+
while(index < chain.length){
|
|
31
|
+
let headerEnd = index;
|
|
32
|
+
while(headerEnd < chain.length && chain[headerEnd] !== ';' && !isChainHeaderEnd(chain, headerEnd)){
|
|
33
|
+
headerEnd += 1;
|
|
34
|
+
}
|
|
35
|
+
const entry = {
|
|
36
|
+
...parseHeader(chain.slice(index, headerEnd)),
|
|
37
|
+
attributes: {}
|
|
38
|
+
};
|
|
39
|
+
entries.push(entry);
|
|
40
|
+
if (chain[headerEnd] !== ':') {
|
|
41
|
+
index = headerEnd + 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
index = headerEnd + 1;
|
|
45
|
+
while(index < chain.length && chain[index] !== ';'){
|
|
46
|
+
const keyEnd = chain.indexOf('="', index);
|
|
47
|
+
if (keyEnd === -1) {
|
|
48
|
+
return entries;
|
|
49
|
+
}
|
|
50
|
+
const { value, end } = readChainValue(chain, keyEnd + 2);
|
|
51
|
+
entry.attributes[chain.slice(index, keyEnd)] = value;
|
|
52
|
+
index = end;
|
|
53
|
+
}
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
return entries;
|
|
57
|
+
}
|
|
58
|
+
export default parseElementsChain;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ // The page path a URL shows: its pathname without the basePath and the outer slashes. It is the
|
|
16
|
+
// page id only for pages without a `path` pattern. The app root gives null.
|
|
17
|
+
function parsePageId(url, basePath = '') {
|
|
18
|
+
const pathname = new URL(url, 'http://localhost').pathname;
|
|
19
|
+
const stripped = basePath && pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
20
|
+
const pageId = stripped.replace(/^\//, '').replace(/\/$/, '');
|
|
21
|
+
return pageId === '' ? null : pageId;
|
|
22
|
+
}
|
|
23
|
+
export default parsePageId;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import type from './type.js';
|
|
16
|
+
// The page id character set without "/", which separates segments.
|
|
17
|
+
const fixedSegmentPattern = /^[A-Za-z0-9\-_:]+$/;
|
|
18
|
+
const placeholderNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
19
|
+
function parsePlaceholder({ path, segment }) {
|
|
20
|
+
const name = segment.slice(1, -1);
|
|
21
|
+
if (name.startsWith('...')) {
|
|
22
|
+
throw new Error(`Page path "${path}" has a catch-all placeholder "${segment}". Catch-all placeholders are not supported.`);
|
|
23
|
+
}
|
|
24
|
+
if (name.startsWith('?') || name.endsWith('?')) {
|
|
25
|
+
throw new Error(`Page path "${path}" has an optional placeholder "${segment}". Optional placeholders are not supported.`);
|
|
26
|
+
}
|
|
27
|
+
if (!placeholderNamePattern.test(name)) {
|
|
28
|
+
throw new Error(`Page path "${path}" has an invalid placeholder "${segment}". Placeholder names must start with a letter or "_" and only contain A-Z, a-z, 0-9 and "_".`);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
name
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function parseSegment({ path, segment }) {
|
|
35
|
+
if (segment === '') {
|
|
36
|
+
throw new Error(`Page path "${path}" has an empty segment.`);
|
|
37
|
+
}
|
|
38
|
+
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
39
|
+
return parsePlaceholder({
|
|
40
|
+
path,
|
|
41
|
+
segment
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (segment.includes('{') || segment.includes('}')) {
|
|
45
|
+
throw new Error(`Page path "${path}" has a placeholder inside segment "${segment}". A placeholder must be a whole segment, like "{id}".`);
|
|
46
|
+
}
|
|
47
|
+
if (!fixedSegmentPattern.test(segment)) {
|
|
48
|
+
throw new Error(`Page path "${path}" segment "${segment}" contains invalid characters. Fixed segments must only contain A-Z, a-z, 0-9, "-", "_" and ":".`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
fixed: segment
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function parseSegments(path) {
|
|
55
|
+
if (path.startsWith('/') || path.endsWith('/')) {
|
|
56
|
+
throw new Error(`Page path "${path}" must not start or end with "/".`);
|
|
57
|
+
}
|
|
58
|
+
const names = new Set();
|
|
59
|
+
return path.split('/').map((segment)=>{
|
|
60
|
+
const parsed = parseSegment({
|
|
61
|
+
path,
|
|
62
|
+
segment
|
|
63
|
+
});
|
|
64
|
+
if (type.isString(parsed.name)) {
|
|
65
|
+
if (names.has(parsed.name)) {
|
|
66
|
+
throw new Error(`Page path "${path}" uses placeholder "${segment}" more than once.`);
|
|
67
|
+
}
|
|
68
|
+
names.add(parsed.name);
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze(parsed);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Patterns come from build config, so the cache holds at most one entry per page path.
|
|
74
|
+
const cache = new Map();
|
|
75
|
+
// Splits a page path pattern into segments, each { fixed } or { name }. The result is shared
|
|
76
|
+
// between callers and frozen.
|
|
77
|
+
function parsePathPattern(path) {
|
|
78
|
+
if (!type.isString(path)) {
|
|
79
|
+
throw new Error(`Page path must be a string. Received ${JSON.stringify(path)}.`);
|
|
80
|
+
}
|
|
81
|
+
if (cache.has(path)) {
|
|
82
|
+
return cache.get(path);
|
|
83
|
+
}
|
|
84
|
+
const segments = Object.freeze(parseSegments(path));
|
|
85
|
+
cache.set(path, segments);
|
|
86
|
+
return segments;
|
|
87
|
+
}
|
|
88
|
+
export default parsePathPattern;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import type from './type.js';
|
|
16
|
+
// An ag-grid `row-index` as a journey target row. Pinned rows (`t-0`, `b-0`) are not zero-based
|
|
17
|
+
// integers, so they give null.
|
|
18
|
+
function parseRowIndex(value) {
|
|
19
|
+
if (!type.isString(value) || !/^\d+$/.test(value)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return Number(value);
|
|
23
|
+
}
|
|
24
|
+
export default parseRowIndex;
|
|
@@ -50,7 +50,10 @@ function projectNode(error, scrub, seen) {
|
|
|
50
50
|
// The server's error sink sets `handled` once it has logged the error, and
|
|
51
51
|
// the client skips reporting a handled error back to the server. A rethrown
|
|
52
52
|
// `_error` without it would be logged a second time.
|
|
53
|
-
handled: error.handled
|
|
53
|
+
handled: error.handled,
|
|
54
|
+
// A ServiceError's Retry-After (seconds, or an HTTP date), so a routine can wait as long
|
|
55
|
+
// as the service asked before retrying, such as an enrichment worker's backoff.
|
|
56
|
+
retryAfter: error.name === 'ServiceError' ? error.retryAfter ?? undefined : undefined
|
|
54
57
|
};
|
|
55
58
|
for (const [key, value] of Object.entries(fields)){
|
|
56
59
|
if (!type.isUndefined(value)) projected[key] = value;
|
|
@@ -69,7 +72,8 @@ function projectNode(error, scrub, seen) {
|
|
|
69
72
|
return projected;
|
|
70
73
|
}
|
|
71
74
|
// The value `_error` reads: the caught error rebuilt with only the fields a
|
|
72
|
-
// routine or catch list branches on
|
|
75
|
+
// routine or catch list branches on (name, message, code, statusCode, handled, a
|
|
76
|
+
// ServiceError's retryAfter, and the cause). `received`, `configKey`, `location`,
|
|
73
77
|
// `source`, `config` and `stack` are left out - `received` can hold a token
|
|
74
78
|
// fetched at runtime that the scrub does not know. The result is an Error, so
|
|
75
79
|
// wherever config sends it on, the serializer's wire policy applies to it.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ // Reads a quoted value from `start` (just past its opening quote). posthog-js escapes a quote
|
|
16
|
+
// in a value as \". Returns the value and the index past its closing quote, or the end of the
|
|
17
|
+
// chain when the quote is never closed.
|
|
18
|
+
function readChainValue(chain, start) {
|
|
19
|
+
let value = '';
|
|
20
|
+
let index = start;
|
|
21
|
+
while(index < chain.length){
|
|
22
|
+
const char = chain[index];
|
|
23
|
+
if (char === '\\' && chain[index + 1] === '"') {
|
|
24
|
+
value += '"';
|
|
25
|
+
index += 2;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (char === '"') {
|
|
29
|
+
return {
|
|
30
|
+
value,
|
|
31
|
+
end: index + 1
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
value += char;
|
|
35
|
+
index += 1;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
value,
|
|
39
|
+
end: index
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export default readChainValue;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import stampHash from './stampHash.js';
|
|
16
|
+
// The stamp hash of a caller's roles in their active organization: the app
|
|
17
|
+
// roles (_user.roles, which page and endpoint auth gates match) and the
|
|
18
|
+
// organization role tier (_user.org_roles: owner, admin or member), since app
|
|
19
|
+
// config can gate blocks on either. Each list is sorted, so the same set always
|
|
20
|
+
// hashes the same, and the pair is JSON-encoded, so no separator inside a role
|
|
21
|
+
// can make two lists read as one. The server stamps it on every response
|
|
22
|
+
// (callerStamp) and the client compares it with the roles of the caller it
|
|
23
|
+
// renders, so both sides use this one function.
|
|
24
|
+
function rolesHash({ user }) {
|
|
25
|
+
return stampHash(JSON.stringify({
|
|
26
|
+
roles: [
|
|
27
|
+
...user.roles ?? []
|
|
28
|
+
].sort(),
|
|
29
|
+
org_roles: [
|
|
30
|
+
...user.org_roles ?? []
|
|
31
|
+
].sort()
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
export default rolesHash;
|
package/dist/serializer.js
CHANGED
|
@@ -206,11 +206,12 @@ const serialize = (json, options = {})=>{
|
|
|
206
206
|
'~d': json.valueOf()
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
|
-
//
|
|
210
|
-
//
|
|
209
|
+
// Markers survive unless the caller passes skipMarkers: config sent to the
|
|
210
|
+
// client needs them to resolve error locations, data does not.
|
|
211
211
|
return JSON.parse(JSON.stringify(json, makeReplacer({
|
|
212
212
|
replacer: options.replacer,
|
|
213
213
|
isoStringDates: options.isoStringDates,
|
|
214
|
+
skipMarkers: options.skipMarkers,
|
|
214
215
|
projectError: options.projectError
|
|
215
216
|
})));
|
|
216
217
|
};
|