assign-gingerly 0.0.74 → 0.0.76
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/DX/installForwarding.js +1 -1
- package/DX/installForwarding.ts +1 -1
- package/README.md +2 -0
- package/assignFrom.js +18 -17
- package/assignFrom.ts +23 -23
- package/assignFromAsync.js +14 -13
- package/assignFromAsync.ts +15 -15
- package/assignGingerly.js +46 -42
- package/assignGingerly.ts +131 -124
- package/assignPermissions/PermissionProcessor.js +166 -0
- package/assignPermissions/PermissionProcessor.ts +210 -0
- package/assignPermissions/isAllowedImportPath.js +2 -6
- package/assignPermissions/isAllowedImportPath.ts +3 -5
- package/assignPermissions/isAllowedUrl.js +18 -0
- package/assignPermissions/isAllowedUrl.ts +15 -0
- package/assignTentatively.js +9 -11
- package/assignTentatively.ts +11 -13
- package/beVigilant.js +1 -1
- package/beVigilant.ts +1 -1
- package/buildCSSQuery.js +1 -1
- package/buildCSSQuery.ts +1 -1
- package/eachTime.js +5 -6
- package/eachTime.ts +12 -15
- package/enhanceAll.js +3 -3
- package/enhanceAll.ts +4 -4
- package/evaluatePathWithAsyncMethods.js +12 -8
- package/evaluatePathWithAsyncMethods.ts +129 -117
- package/handlers/addEventListener.js +11 -11
- package/handlers/addEventListener.ts +12 -13
- package/handlers/lazyLoad.ts +7 -7
- package/handlers/lazyLoadSwitch.ts +3 -3
- package/handlers/manageTemplateList.js +10 -10
- package/handlers/manageTemplateList.ts +12 -12
- package/handlers/rangeSelector.ts +3 -3
- package/index.js +2 -3
- package/index.ts +2 -3
- package/inferencer/types/assign-gingerly/types.d.ts +37 -23
- package/inferencer/types/el-maker/types.d.ts +33 -0
- package/inferencer/types/scratch-box/types.d.ts +3 -0
- package/inferencer/types/truth-sourcer/types.d.ts +4 -0
- package/package.json +25 -23
- package/parseWithAttrs.js +1 -1
- package/parseWithAttrs.ts +1 -1
- package/processHandlerCommands.js +5 -7
- package/processHandlerCommands.ts +12 -15
- package/{getValues.js → resolve/getValues.js} +19 -8
- package/{getValues.ts → resolve/getValues.ts} +331 -314
- package/{resolveIdRef.js → resolve/resolveIdRef.js} +1 -1
- package/{resolveIdRef.ts → resolve/resolveIdRef.ts} +1 -1
- package/{resolveValues.ts → resolve/resolveValues.ts} +1 -1
- package/types/assign-gingerly/types.d.ts +37 -8
- package/utils/findClassPrototypeInPath.js +57 -0
- package/utils/findClassPrototypeInPath.ts +62 -0
- package/utils/isAsyncSpawn.js +20 -0
- package/utils/isAsyncSpawn.ts +17 -0
- package/assignPermissions/restrictedProps.js +0 -43
- package/assignPermissions/restrictedProps.ts +0 -53
- package/resolveAndAssignFeatures.js +0 -36
- package/resolveAndAssignFeatures.ts +0 -43
- /package/{resolveTemplate.js → resolve/resolveTemplate.js} +0 -0
- /package/{resolveTemplate.ts → resolve/resolveTemplate.ts} +0 -0
- /package/{resolveValues.js → resolve/resolveValues.js} +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { AssignPermissions, RestrictedPropSetting } from '../types/assign-gingerly/types.js';
|
|
2
|
+
import { isAllowedUrl } from './isAllowedUrl.js';
|
|
3
|
+
|
|
4
|
+
export interface RestrictedPropSettingsMap {
|
|
5
|
+
props: Map<string, RestrictedPropSetting | undefined>;
|
|
6
|
+
attrs: Map<string, RestrictedPropSetting | undefined>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class PermissionProcessor {
|
|
10
|
+
private readonly permissions: AssignPermissions | undefined;
|
|
11
|
+
private readonly props: Map<string, RestrictedPropSetting | undefined>;
|
|
12
|
+
private readonly attrs: Map<string, RestrictedPropSetting | undefined>;
|
|
13
|
+
private readonly methods: Set<string>;
|
|
14
|
+
private readonly warned = new Set<string>();
|
|
15
|
+
private readonly warnedMethods = new Set<string>();
|
|
16
|
+
|
|
17
|
+
constructor(permissions: AssignPermissions | undefined) {
|
|
18
|
+
this.permissions = permissions;
|
|
19
|
+
const { props, attrs } = buildMaps(permissions);
|
|
20
|
+
this.props = props;
|
|
21
|
+
this.attrs = attrs;
|
|
22
|
+
this.methods = buildMethodSet(permissions);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get crossDomainImports(): boolean {
|
|
26
|
+
return !!this.permissions?.crossDomainImports;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get hasProps(): boolean {
|
|
30
|
+
return this.props.size > 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get hasAttrs(): boolean {
|
|
34
|
+
return this.attrs.size > 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
checkRestrictedProp(key: string): boolean {
|
|
38
|
+
if (!this.props.has(key)) return false;
|
|
39
|
+
this.warnRestricted(key);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
checkRestrictedMethod(methodName: string): boolean {
|
|
44
|
+
if (!this.methods.has(methodName)) return false;
|
|
45
|
+
this.warnRestrictedMethod(methodName);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
redirectRestrictedProp(target: any, key: string, value: any): boolean {
|
|
50
|
+
if (!this.props.has(key)) return false;
|
|
51
|
+
const setting = this.props.get(key);
|
|
52
|
+
const { allowCrossDomain, allowFromSameDomain } = setting || {};
|
|
53
|
+
|
|
54
|
+
if (allowCrossDomain) {
|
|
55
|
+
return this.applyAllowedSetting(setting, target, key, value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (allowFromSameDomain) {
|
|
59
|
+
if (isAllowedUrl(value)) {
|
|
60
|
+
return this.applyAllowedSetting(setting, target, key, value);
|
|
61
|
+
}
|
|
62
|
+
this.warnRestricted(key);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (setting?.useMethod) {
|
|
67
|
+
const method = target?.[setting.useMethod];
|
|
68
|
+
if (typeof method === 'function') {
|
|
69
|
+
method.call(target, value);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.warnRestricted(key);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
checkRestrictedAttributeCall(
|
|
79
|
+
methodName: string,
|
|
80
|
+
args: any[]
|
|
81
|
+
): { blocked: boolean; attrName?: string } {
|
|
82
|
+
if (methodName !== 'setAttribute' || args.length === 0 || this.attrs.size === 0) {
|
|
83
|
+
return { blocked: false };
|
|
84
|
+
}
|
|
85
|
+
const attrName = String(args[0]);
|
|
86
|
+
if (!this.attrs.has(attrName)) {
|
|
87
|
+
return { blocked: false };
|
|
88
|
+
}
|
|
89
|
+
const setting = this.attrs.get(attrName);
|
|
90
|
+
const value = args[1];
|
|
91
|
+
const { allowCrossDomain, allowFromSameDomain } = setting || {};
|
|
92
|
+
|
|
93
|
+
if (allowCrossDomain) {
|
|
94
|
+
return { blocked: false, attrName };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (allowFromSameDomain) {
|
|
98
|
+
if (isAllowedUrl(value)) {
|
|
99
|
+
return { blocked: false, attrName };
|
|
100
|
+
}
|
|
101
|
+
this.warnRestricted(attrName);
|
|
102
|
+
return { blocked: true, attrName };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.warnRestricted(attrName);
|
|
106
|
+
return { blocked: true, attrName };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private applyAllowedSetting(
|
|
110
|
+
setting: RestrictedPropSetting | undefined,
|
|
111
|
+
target: any,
|
|
112
|
+
key: string,
|
|
113
|
+
value: any
|
|
114
|
+
): boolean {
|
|
115
|
+
if (setting?.useMethod) {
|
|
116
|
+
const method = target?.[setting.useMethod];
|
|
117
|
+
if (typeof method === 'function') {
|
|
118
|
+
method.call(target, value);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
this.warnRestricted(key);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private warnRestricted(key: string): void {
|
|
128
|
+
if (!this.warned.has(key)) {
|
|
129
|
+
this.warned.add(key);
|
|
130
|
+
console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private warnRestrictedMethod(methodName: string): void {
|
|
135
|
+
if (!this.warnedMethods.has(methodName)) {
|
|
136
|
+
this.warnedMethods.add(methodName);
|
|
137
|
+
console.warn(`assignGingerly: method '${methodName}' is in restrictedMethodSettings — method call skipped.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildMaps(permissions: AssignPermissions | undefined): RestrictedPropSettingsMap {
|
|
143
|
+
const settings = permissions?.restrictedPropSettings;
|
|
144
|
+
const props = new Map<string, RestrictedPropSetting | undefined>();
|
|
145
|
+
const attrs = new Map<string, RestrictedPropSetting | undefined>();
|
|
146
|
+
if (!settings || settings.length === 0) {
|
|
147
|
+
return { props, attrs };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const setting of settings) {
|
|
151
|
+
if (typeof setting === 'string') {
|
|
152
|
+
if (props.has(setting)) {
|
|
153
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${setting}'.`);
|
|
154
|
+
}
|
|
155
|
+
props.set(setting, undefined);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const propNames = normalizeStrings(setting.props);
|
|
160
|
+
if (propNames.length === 0) {
|
|
161
|
+
throw new Error('assignGingerly: restrictedPropSettings object entry must have a props value.');
|
|
162
|
+
}
|
|
163
|
+
for (const prop of propNames) {
|
|
164
|
+
if (props.has(prop)) {
|
|
165
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
|
|
166
|
+
}
|
|
167
|
+
props.set(prop, setting);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const attrNames = normalizeAttrNames(setting.attr, propNames);
|
|
171
|
+
for (const attr of attrNames) {
|
|
172
|
+
if (attrs.has(attr)) {
|
|
173
|
+
throw new Error(`assignGingerly: duplicate restrictedPropSettings attr entry for '${attr}'.`);
|
|
174
|
+
}
|
|
175
|
+
attrs.set(attr, setting);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { props, attrs };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeStrings(value: string | string[] | undefined): string[] {
|
|
182
|
+
if (value === undefined) return [];
|
|
183
|
+
return Array.isArray(value) ? value : [value];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeAttrNames(
|
|
187
|
+
attr: boolean | string | string[] | undefined,
|
|
188
|
+
propNames: string[]
|
|
189
|
+
): string[] {
|
|
190
|
+
if (attr === undefined || attr === false) return [];
|
|
191
|
+
if (attr === true) return propNames;
|
|
192
|
+
return normalizeStrings(attr);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function buildMethodSet(permissions: AssignPermissions | undefined): Set<string> {
|
|
196
|
+
const methodSettings = permissions?.restrictedMethodSettings;
|
|
197
|
+
const methods = new Set<string>();
|
|
198
|
+
if (!methodSettings || methodSettings.length === 0) {
|
|
199
|
+
return methods;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const setting of methodSettings) {
|
|
203
|
+
if (typeof setting === 'string') {
|
|
204
|
+
methods.add(setting);
|
|
205
|
+
}
|
|
206
|
+
// Phase II: object-form RestrictedMethodConfig entries are ignored for now.
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return methods;
|
|
210
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAllowedUrl } from './isAllowedUrl.js';
|
|
1
2
|
/**
|
|
2
3
|
* Check whether an import specifier resolves to the current origin or is
|
|
3
4
|
* covered by an import-map entry.
|
|
@@ -8,12 +9,7 @@ export function isAllowedImportPath(value) {
|
|
|
8
9
|
if (!value || !(value.startsWith('./') || value.startsWith('../') || value.startsWith('/'))) {
|
|
9
10
|
return isImportMapSpecifier(value);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
return new URL(value, document.baseURI).origin === location.origin;
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
12
|
+
return isAllowedUrl(value);
|
|
17
13
|
}
|
|
18
14
|
function isImportMapSpecifier(value) {
|
|
19
15
|
const importMaps = document.querySelectorAll('script[type="importmap"]');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isAllowedUrl } from './isAllowedUrl.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Check whether an import specifier resolves to the current origin or is
|
|
3
5
|
* covered by an import-map entry.
|
|
@@ -8,11 +10,7 @@ export function isAllowedImportPath(value: string): boolean {
|
|
|
8
10
|
return isImportMapSpecifier(value);
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
return new URL(value, document.baseURI).origin === location.origin;
|
|
13
|
-
} catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
13
|
+
return isAllowedUrl(value);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
function isImportMapSpecifier(value: string): boolean {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether a URL string resolves to the current origin.
|
|
3
|
+
*
|
|
4
|
+
* Returns false for non-strings, empty strings, malformed URLs, or
|
|
5
|
+
* any URL whose origin differs from `location.origin`.
|
|
6
|
+
*/
|
|
7
|
+
export function isAllowedUrl(value, base = document.baseURI) {
|
|
8
|
+
if (typeof document === 'undefined' || typeof location === 'undefined')
|
|
9
|
+
return false;
|
|
10
|
+
if (typeof value !== 'string' || value === '')
|
|
11
|
+
return false;
|
|
12
|
+
try {
|
|
13
|
+
return new URL(value, base).origin === location.origin;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether a URL string resolves to the current origin.
|
|
3
|
+
*
|
|
4
|
+
* Returns false for non-strings, empty strings, malformed URLs, or
|
|
5
|
+
* any URL whose origin differs from `location.origin`.
|
|
6
|
+
*/
|
|
7
|
+
export function isAllowedUrl(value: string, base = document.baseURI): boolean {
|
|
8
|
+
if (typeof document === 'undefined' || typeof location === 'undefined') return false;
|
|
9
|
+
if (typeof value !== 'string' || value === '') return false;
|
|
10
|
+
try {
|
|
11
|
+
return new URL(value, base).origin === location.origin;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/assignTentatively.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { buildRestrictedPropSet, checkRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
2
1
|
/**
|
|
3
2
|
* Helper function to check if a string key represents an += command
|
|
4
3
|
*/
|
|
@@ -114,13 +113,12 @@ function getTopLevelKey(path) {
|
|
|
114
113
|
/**
|
|
115
114
|
* Main assignTentatively function with reversal support
|
|
116
115
|
*/
|
|
117
|
-
export function assignTentatively(target, source, options,
|
|
116
|
+
export function assignTentatively(target, source, options, permissionProcessor) {
|
|
118
117
|
if (!target || typeof target !== 'object') {
|
|
119
118
|
return target;
|
|
120
119
|
}
|
|
121
120
|
const reversal = options?.reversal || {};
|
|
122
121
|
const trackedCreatedPaths = new Set();
|
|
123
|
-
const restrictedPropSet = buildRestrictedPropSet(permissions);
|
|
124
122
|
// Process all keys from source
|
|
125
123
|
for (const key of Object.keys(source)) {
|
|
126
124
|
const value = source[key];
|
|
@@ -132,7 +130,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
132
130
|
const pathParts = parsePath(path);
|
|
133
131
|
const topLevelKey = pathParts[0];
|
|
134
132
|
const lastKey = pathParts[pathParts.length - 1];
|
|
135
|
-
if (checkRestrictedProp(
|
|
133
|
+
if (permissionProcessor?.checkRestrictedProp(lastKey)) {
|
|
136
134
|
continue;
|
|
137
135
|
}
|
|
138
136
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
@@ -155,7 +153,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
155
153
|
}
|
|
156
154
|
else {
|
|
157
155
|
// Plain key - direct operation on target
|
|
158
|
-
if (checkRestrictedProp(
|
|
156
|
+
if (permissionProcessor?.checkRestrictedProp(path)) {
|
|
159
157
|
continue;
|
|
160
158
|
}
|
|
161
159
|
if (path in target) {
|
|
@@ -183,7 +181,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
183
181
|
lhsPathParts = parsePath(lhsPath);
|
|
184
182
|
const topLevelKey = lhsPathParts[0];
|
|
185
183
|
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
186
|
-
if (checkRestrictedProp(
|
|
184
|
+
if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
187
185
|
continue;
|
|
188
186
|
}
|
|
189
187
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
@@ -195,7 +193,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
195
193
|
else {
|
|
196
194
|
lhsPathParts = [lhsPath];
|
|
197
195
|
lhsLastKey = lhsPath;
|
|
198
|
-
if (checkRestrictedProp(
|
|
196
|
+
if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
199
197
|
continue;
|
|
200
198
|
}
|
|
201
199
|
lhsParent = target;
|
|
@@ -301,7 +299,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
301
299
|
const pathParts = parsePath(key);
|
|
302
300
|
const topLevelKey = pathParts[0];
|
|
303
301
|
const lastKey = pathParts[pathParts.length - 1];
|
|
304
|
-
if (checkRestrictedProp(
|
|
302
|
+
if (permissionProcessor?.checkRestrictedProp(lastKey)) {
|
|
305
303
|
continue;
|
|
306
304
|
}
|
|
307
305
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
@@ -323,7 +321,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
323
321
|
}
|
|
324
322
|
// For nested objects, recursively apply with nested reversal tracking
|
|
325
323
|
const nestedReversal = {};
|
|
326
|
-
assignTentatively(parent[lastKey], value, { reversal: nestedReversal },
|
|
324
|
+
assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissionProcessor);
|
|
327
325
|
// Merge nested reversals
|
|
328
326
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
329
327
|
if (!(revKey in reversal)) {
|
|
@@ -344,7 +342,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
344
342
|
}
|
|
345
343
|
else {
|
|
346
344
|
// Non-nested key
|
|
347
|
-
if (checkRestrictedProp(
|
|
345
|
+
if (permissionProcessor?.checkRestrictedProp(key)) {
|
|
348
346
|
continue;
|
|
349
347
|
}
|
|
350
348
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
@@ -359,7 +357,7 @@ export function assignTentatively(target, source, options, permissions) {
|
|
|
359
357
|
target[key] = {};
|
|
360
358
|
}
|
|
361
359
|
const nestedReversal = {};
|
|
362
|
-
assignTentatively(target[key], value, { reversal: nestedReversal },
|
|
360
|
+
assignTentatively(target[key], value, { reversal: nestedReversal }, permissionProcessor);
|
|
363
361
|
// Merge nested reversals
|
|
364
362
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
365
363
|
if (!(revKey in reversal)) {
|
package/assignTentatively.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* assignTentatively — reversible assignment with change tracking.
|
|
3
3
|
*/
|
|
4
|
-
import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
|
|
5
|
-
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
6
|
-
import { buildRestrictedPropSet, checkRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
4
|
+
import type { IAssignTentativelyOptions, PermissionProcessor } from './types/assign-gingerly/types.js';
|
|
7
5
|
export type { IAssignTentativelyOptions };
|
|
8
6
|
|
|
9
7
|
/**
|
|
@@ -137,7 +135,7 @@ export function assignTentatively(
|
|
|
137
135
|
target: any,
|
|
138
136
|
source: Record<string | symbol, any>,
|
|
139
137
|
options?: IAssignTentativelyOptions,
|
|
140
|
-
|
|
138
|
+
permissionProcessor?: PermissionProcessor
|
|
141
139
|
): any {
|
|
142
140
|
if (!target || typeof target !== 'object') {
|
|
143
141
|
return target;
|
|
@@ -145,7 +143,7 @@ export function assignTentatively(
|
|
|
145
143
|
|
|
146
144
|
const reversal = options?.reversal || {};
|
|
147
145
|
const trackedCreatedPaths = new Set<string>();
|
|
148
|
-
|
|
146
|
+
|
|
149
147
|
|
|
150
148
|
// Process all keys from source
|
|
151
149
|
for (const key of Object.keys(source)) {
|
|
@@ -160,7 +158,7 @@ export function assignTentatively(
|
|
|
160
158
|
const topLevelKey = pathParts[0];
|
|
161
159
|
const lastKey = pathParts[pathParts.length - 1];
|
|
162
160
|
|
|
163
|
-
if (checkRestrictedProp(
|
|
161
|
+
if (permissionProcessor?.checkRestrictedProp(lastKey)) {
|
|
164
162
|
continue;
|
|
165
163
|
}
|
|
166
164
|
|
|
@@ -184,7 +182,7 @@ export function assignTentatively(
|
|
|
184
182
|
}
|
|
185
183
|
} else {
|
|
186
184
|
// Plain key - direct operation on target
|
|
187
|
-
if (checkRestrictedProp(
|
|
185
|
+
if (permissionProcessor?.checkRestrictedProp(path)) {
|
|
188
186
|
continue;
|
|
189
187
|
}
|
|
190
188
|
if (path in target) {
|
|
@@ -215,7 +213,7 @@ export function assignTentatively(
|
|
|
215
213
|
const topLevelKey = lhsPathParts[0];
|
|
216
214
|
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
217
215
|
|
|
218
|
-
if (checkRestrictedProp(
|
|
216
|
+
if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
219
217
|
continue;
|
|
220
218
|
}
|
|
221
219
|
|
|
@@ -228,7 +226,7 @@ export function assignTentatively(
|
|
|
228
226
|
} else {
|
|
229
227
|
lhsPathParts = [lhsPath];
|
|
230
228
|
lhsLastKey = lhsPath;
|
|
231
|
-
if (checkRestrictedProp(
|
|
229
|
+
if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
|
|
232
230
|
continue;
|
|
233
231
|
}
|
|
234
232
|
lhsParent = target;
|
|
@@ -337,7 +335,7 @@ export function assignTentatively(
|
|
|
337
335
|
const topLevelKey = pathParts[0];
|
|
338
336
|
const lastKey = pathParts[pathParts.length - 1];
|
|
339
337
|
|
|
340
|
-
if (checkRestrictedProp(
|
|
338
|
+
if (permissionProcessor?.checkRestrictedProp(lastKey)) {
|
|
341
339
|
continue;
|
|
342
340
|
}
|
|
343
341
|
|
|
@@ -362,7 +360,7 @@ export function assignTentatively(
|
|
|
362
360
|
}
|
|
363
361
|
// For nested objects, recursively apply with nested reversal tracking
|
|
364
362
|
const nestedReversal: Record<string | symbol, any> = {};
|
|
365
|
-
assignTentatively(parent[lastKey], value, { reversal: nestedReversal },
|
|
363
|
+
assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissionProcessor);
|
|
366
364
|
// Merge nested reversals
|
|
367
365
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
368
366
|
if (!(revKey in reversal)) {
|
|
@@ -381,7 +379,7 @@ export function assignTentatively(
|
|
|
381
379
|
}
|
|
382
380
|
} else {
|
|
383
381
|
// Non-nested key
|
|
384
|
-
if (checkRestrictedProp(
|
|
382
|
+
if (permissionProcessor?.checkRestrictedProp(key)) {
|
|
385
383
|
continue;
|
|
386
384
|
}
|
|
387
385
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
@@ -396,7 +394,7 @@ export function assignTentatively(
|
|
|
396
394
|
target[key] = {};
|
|
397
395
|
}
|
|
398
396
|
const nestedReversal: Record<string | symbol, any> = {};
|
|
399
|
-
assignTentatively(target[key], value, { reversal: nestedReversal },
|
|
397
|
+
assignTentatively(target[key], value, { reversal: nestedReversal }, permissionProcessor);
|
|
400
398
|
// Merge nested reversals
|
|
401
399
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
402
400
|
if (!(revKey in reversal)) {
|
package/beVigilant.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Infer } from './inferencer/inferencer.js';
|
|
11
11
|
import { withScopePerimeter } from './inferencer/withScopePerimeter.js';
|
|
12
|
-
import { registerInCache } from './resolveIdRef.js';
|
|
12
|
+
import { registerInCache } from './resolve/resolveIdRef.js';
|
|
13
13
|
/**
|
|
14
14
|
* Set up a MutationObserver that watches for new [itemprop] elements
|
|
15
15
|
* and applies inferred assignments from the live `from` object.
|
package/beVigilant.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { Infer } from './inferencer/inferencer.js';
|
|
12
12
|
import { withScopePerimeter } from './inferencer/withScopePerimeter.js';
|
|
13
|
-
import { registerInCache } from './resolveIdRef.js';
|
|
13
|
+
import { registerInCache } from './resolve/resolveIdRef.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Set up a MutationObserver that watches for new [itemprop] elements
|
package/buildCSSQuery.js
CHANGED
package/buildCSSQuery.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EnhancementConfig, AttrPatterns } from './types/assign-gingerly/types';
|
|
2
|
-
import { resolveTemplate } from './resolveTemplate.js';
|
|
2
|
+
import { resolveTemplate } from './resolve/resolveTemplate.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Extracts attribute names from withAttrs configuration
|
package/eachTime.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* This module is dynamically loaded only when @eachTime is encountered
|
|
4
4
|
* Provides event-driven iteration over elements as they mount
|
|
5
5
|
*/
|
|
6
|
-
import { redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
7
6
|
/**
|
|
8
7
|
* Check if a value is an EventTarget
|
|
9
8
|
*/
|
|
@@ -21,7 +20,7 @@ function isEventTarget(value) {
|
|
|
21
20
|
* @param aliasMap - Map of aliases for token substitution
|
|
22
21
|
* @param options - Options including required AbortSignal
|
|
23
22
|
*/
|
|
24
|
-
export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options,
|
|
23
|
+
export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options, permissionProcessor) {
|
|
25
24
|
// Validate signal - required for cleanup
|
|
26
25
|
if (!options?.signal) {
|
|
27
26
|
throw new Error('@eachTime requires an AbortSignal in options.signal for cleanup');
|
|
@@ -35,7 +34,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
35
34
|
// Import evaluatePathWithMethods for navigation
|
|
36
35
|
const { evaluatePathWithMethods } = await import('./assignGingerly.js');
|
|
37
36
|
if (withMethods && withMethods.size > 0) {
|
|
38
|
-
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods);
|
|
37
|
+
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods, permissionProcessor);
|
|
39
38
|
eventSource = result.target;
|
|
40
39
|
}
|
|
41
40
|
else {
|
|
@@ -61,7 +60,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
61
60
|
// Import needed functions from assignGingerly
|
|
62
61
|
const { evaluatePathWithMethods, assignGingerly, isReadonlyProperty } = await import('./assignGingerly.js');
|
|
63
62
|
if (pathAfterForEach.length > 0) {
|
|
64
|
-
const result = evaluatePathWithMethods(mountedElement, pathAfterForEach, value, withMethods || new Set());
|
|
63
|
+
const result = evaluatePathWithMethods(mountedElement, pathAfterForEach, value, withMethods || new Set(), permissionProcessor);
|
|
65
64
|
if (result.isMethod) {
|
|
66
65
|
// Last segment is a method - call it
|
|
67
66
|
const method = result.target[result.lastKey];
|
|
@@ -82,7 +81,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
82
81
|
// Normal assignment
|
|
83
82
|
const lastKey = result.lastKey;
|
|
84
83
|
const parent = result.target;
|
|
85
|
-
if (redirectRestrictedProp(
|
|
84
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
86
85
|
// skip
|
|
87
86
|
}
|
|
88
87
|
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
@@ -93,7 +92,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
93
92
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
94
93
|
}
|
|
95
94
|
// Recursively apply assignGingerly
|
|
96
|
-
assignGingerly(currentValue, value, options,
|
|
95
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
97
96
|
}
|
|
98
97
|
else {
|
|
99
98
|
// Property is writable - replace it
|
package/eachTime.ts
CHANGED
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
* Provides event-driven iteration over elements as they mount
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
|
|
8
|
-
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
9
|
-
import { redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
10
|
-
import type { RestrictedPropSettingsMap } from './assignPermissions/restrictedProps.js';
|
|
7
|
+
import type { PermissionProcessor, IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* Check if a value is an EventTarget
|
|
@@ -32,11 +29,10 @@ export async function handleEachTime(
|
|
|
32
29
|
pathParts: string[],
|
|
33
30
|
forEachIndex: number,
|
|
34
31
|
value: any,
|
|
35
|
-
withMethods: Set<string> | undefined,
|
|
36
|
-
aliasMap: Map<string, string>,
|
|
37
|
-
options?: IAssignGingerlyOptions,
|
|
38
|
-
|
|
39
|
-
restrictedPropSet?: RestrictedPropSettingsMap
|
|
32
|
+
withMethods: Set<string> | undefined,
|
|
33
|
+
aliasMap: Map<string, string>,
|
|
34
|
+
options?: IAssignGingerlyOptions,
|
|
35
|
+
permissionProcessor?: PermissionProcessor
|
|
40
36
|
): Promise<void> {
|
|
41
37
|
// Validate signal - required for cleanup
|
|
42
38
|
if (!options?.signal) {
|
|
@@ -54,7 +50,7 @@ export async function handleEachTime(
|
|
|
54
50
|
const { evaluatePathWithMethods } = await import('./assignGingerly.js');
|
|
55
51
|
|
|
56
52
|
if (withMethods && withMethods.size > 0) {
|
|
57
|
-
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods);
|
|
53
|
+
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods, permissionProcessor);
|
|
58
54
|
eventSource = result.target;
|
|
59
55
|
} else {
|
|
60
56
|
// Simple property navigation
|
|
@@ -90,7 +86,8 @@ export async function handleEachTime(
|
|
|
90
86
|
mountedElement,
|
|
91
87
|
pathAfterForEach,
|
|
92
88
|
value,
|
|
93
|
-
withMethods || new Set()
|
|
89
|
+
withMethods || new Set(),
|
|
90
|
+
permissionProcessor
|
|
94
91
|
);
|
|
95
92
|
|
|
96
93
|
if (result.isMethod) {
|
|
@@ -111,9 +108,9 @@ export async function handleEachTime(
|
|
|
111
108
|
const lastKey = result.lastKey;
|
|
112
109
|
const parent = result.target;
|
|
113
110
|
|
|
114
|
-
if (redirectRestrictedProp(
|
|
115
|
-
// skip
|
|
116
|
-
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
111
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
112
|
+
// skip
|
|
113
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
117
114
|
// Check if property exists and is readonly
|
|
118
115
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
119
116
|
const currentValue = parent[lastKey];
|
|
@@ -123,7 +120,7 @@ export async function handleEachTime(
|
|
|
123
120
|
);
|
|
124
121
|
}
|
|
125
122
|
// Recursively apply assignGingerly
|
|
126
|
-
assignGingerly(currentValue, value, options,
|
|
123
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
127
124
|
} else {
|
|
128
125
|
// Property is writable - replace it
|
|
129
126
|
parent[lastKey] = value;
|
package/enhanceAll.js
CHANGED
|
@@ -33,13 +33,13 @@ import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
|
33
33
|
* @param target - The DOM element to search within
|
|
34
34
|
* @param configs - Array of enhancement configurations
|
|
35
35
|
*/
|
|
36
|
-
export async function enhanceAll(target, configs,
|
|
36
|
+
export async function enhanceAll(target, configs, permissionProcessor) {
|
|
37
37
|
for (const config of configs) {
|
|
38
38
|
// Validate EMC path unless cross-domain imports are explicitly permitted
|
|
39
|
-
if (!
|
|
39
|
+
if (!permissionProcessor?.crossDomainImports && !isAllowedImportPath(config.emc)) {
|
|
40
40
|
throw new Error(`enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
|
|
41
41
|
`Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
|
|
42
|
-
`Pass { crossDomainImports: true } in
|
|
42
|
+
`Pass { crossDomainImports: true } in permissionProcessor to override.`);
|
|
43
43
|
}
|
|
44
44
|
// 1. Import the EMC JSON
|
|
45
45
|
const emcModule = await import(config.emc, { with: { type: 'json' } });
|