what-core 0.6.5 → 0.6.7
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/compiler.js +59 -15
- package/dist/compiler.js.map +3 -3
- package/dist/compiler.min.js +1 -1
- package/dist/compiler.min.js.map +4 -4
- package/dist/index.js +59 -15
- package/dist/index.js.map +3 -3
- package/dist/index.min.js +5 -5
- package/dist/index.min.js.map +4 -4
- package/dist/render.js +59 -15
- package/dist/render.js.map +3 -3
- package/dist/render.min.js +2 -2
- package/dist/render.min.js.map +4 -4
- package/dist/testing.js +54 -0
- package/dist/testing.js.map +3 -3
- package/dist/testing.min.js +1 -1
- package/dist/testing.min.js.map +4 -4
- package/hooks.d.ts +25 -0
- package/package.json +3 -1
- package/src/dom.js +9 -0
- package/src/render.js +6 -20
- package/src/security.js +63 -0
package/hooks.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// What Framework - Hooks Type Definitions
|
|
2
|
+
|
|
3
|
+
import { Signal } from './index.js';
|
|
4
|
+
|
|
5
|
+
export function useState<T>(initial: T | (() => T)): [Signal<T>, (value: T | ((prev: T) => T)) => void];
|
|
6
|
+
export function useSignal<T>(initial: T): Signal<T>;
|
|
7
|
+
export function useComputed<T>(fn: () => T): Signal<T>;
|
|
8
|
+
export function useEffect(fn: () => void | (() => void), deps?: any[]): void;
|
|
9
|
+
export function useMemo<T>(fn: () => T, deps?: any[]): T;
|
|
10
|
+
export function useCallback<T extends (...args: any[]) => any>(fn: T, deps?: any[]): T;
|
|
11
|
+
export function useRef<T>(initial?: T): { current: T };
|
|
12
|
+
export function useContext<T>(context: { _defaultValue: T }): T;
|
|
13
|
+
export function createContext<T>(defaultValue?: T): { _defaultValue: T; Provider: (props: { value: T; children: any }) => any };
|
|
14
|
+
export function useReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S, init?: (s: S) => S): [Signal<S>, (action: A) => void];
|
|
15
|
+
export function onMount(fn: () => void | (() => void)): void;
|
|
16
|
+
export function onCleanup(fn: () => void): void;
|
|
17
|
+
|
|
18
|
+
export interface Resource<T> {
|
|
19
|
+
(): T | undefined;
|
|
20
|
+
loading: Signal<boolean>;
|
|
21
|
+
error: Signal<Error | null>;
|
|
22
|
+
refetch: () => void;
|
|
23
|
+
mutate: (value: T) => void;
|
|
24
|
+
}
|
|
25
|
+
export function createResource<T>(fetcher: () => Promise<T>, options?: { initialValue?: T }): Resource<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "What Framework - The closest framework to vanilla JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"import": "./src/testing.js"
|
|
34
34
|
},
|
|
35
35
|
"./hooks": {
|
|
36
|
+
"types": "./hooks.d.ts",
|
|
36
37
|
"import": "./src/hooks.js"
|
|
37
38
|
},
|
|
38
39
|
"./compiler": {
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"jsx-runtime.d.ts",
|
|
54
55
|
"render.d.ts",
|
|
55
56
|
"testing.d.ts",
|
|
57
|
+
"hooks.d.ts",
|
|
56
58
|
"compiler.d.ts",
|
|
57
59
|
"devtools.d.ts"
|
|
58
60
|
],
|
package/src/dom.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { effect, batch, untrack, signal, __DEV__, __devtools } from './reactive.js';
|
|
6
6
|
import { reportError, _injectGetCurrentComponent, shallowEqual } from './components.js';
|
|
7
7
|
import { _setComponentRef } from './helpers.js';
|
|
8
|
+
import { getDomAttributeName, isSafeUrlAttributeValue } from './security.js';
|
|
8
9
|
|
|
9
10
|
// SVG elements that need namespace
|
|
10
11
|
const SVG_ELEMENTS = new Set([
|
|
@@ -588,6 +589,14 @@ function applyProps(el, newProps, oldProps, isSvg) {
|
|
|
588
589
|
}
|
|
589
590
|
|
|
590
591
|
function setProp(el, key, value, isSvg) {
|
|
592
|
+
// Sanitize URL attributes before property assignment or setAttribute can commit them.
|
|
593
|
+
if (!isSafeUrlAttributeValue(key, value)) {
|
|
594
|
+
if (typeof console !== 'undefined') {
|
|
595
|
+
console.warn(`[what] Blocked unsafe URL in "${key}" attribute: ${value}`);
|
|
596
|
+
}
|
|
597
|
+
el.removeAttribute(getDomAttributeName(key));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
591
600
|
// Reactive function props — wrap in effect for fine-grained updates
|
|
592
601
|
if (typeof value === 'function' && !(key.startsWith('on') && key.length > 2) && key !== 'ref') {
|
|
593
602
|
if (!el._propEffects) el._propEffects = {};
|
package/src/render.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { effect, untrack, createRoot, signal, __DEV__, __devtools } from './reactive.js';
|
|
6
6
|
import { createDOM, disposeTree, getCurrentComponent, getComponentStack } from './dom.js';
|
|
7
7
|
import { createWhatError, collectError } from './errors.js';
|
|
8
|
+
import { getDomAttributeName, isSafeUrlAttributeValue } from './security.js';
|
|
8
9
|
|
|
9
10
|
export { effect, untrack };
|
|
10
11
|
|
|
@@ -22,20 +23,6 @@ export function _$createComponent(Component, props, children) {
|
|
|
22
23
|
return createDOM({ tag: Component, props: props || {}, children: children || [], key: null, _vnode: true });
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
// --- URL Sanitization for DOM attributes ---
|
|
26
|
-
// Rejects javascript:, data:, vbscript: protocols (case-insensitive, trimmed).
|
|
27
|
-
|
|
28
|
-
const URL_ATTRS = new Set(['href', 'src', 'action', 'formaction', 'formAction']);
|
|
29
|
-
|
|
30
|
-
function isSafeUrl(url) {
|
|
31
|
-
if (typeof url !== 'string') return true; // non-string values are not URL-injection risks
|
|
32
|
-
const normalized = url.trim().replace(/[\s\x00-\x1f]/g, '').toLowerCase();
|
|
33
|
-
if (normalized.startsWith('javascript:')) return false;
|
|
34
|
-
if (normalized.startsWith('data:')) return false;
|
|
35
|
-
if (normalized.startsWith('vbscript:')) return false;
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
26
|
// --- template(html) ---
|
|
40
27
|
// Pre-parse HTML string into a <template> element. Returns a factory function
|
|
41
28
|
// that clones the DOM tree via cloneNode(true) — 2-5x faster than createElement chains.
|
|
@@ -870,13 +857,12 @@ export function setProp(el, key, value) {
|
|
|
870
857
|
if (key === 'key') return;
|
|
871
858
|
|
|
872
859
|
// Sanitize URL attributes — reject dangerous protocols
|
|
873
|
-
if (
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
console.warn(`[what] Blocked unsafe URL in "${key}" attribute: ${value}`);
|
|
877
|
-
}
|
|
878
|
-
return;
|
|
860
|
+
if (!isSafeUrlAttributeValue(key, value)) {
|
|
861
|
+
if (typeof console !== 'undefined') {
|
|
862
|
+
console.warn(`[what] Blocked unsafe URL in "${key}" attribute: ${value}`);
|
|
879
863
|
}
|
|
864
|
+
el.removeAttribute(getDomAttributeName(key));
|
|
865
|
+
return;
|
|
880
866
|
}
|
|
881
867
|
|
|
882
868
|
if (key === 'class' || key === 'className') {
|
package/src/security.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// What Framework - DOM/SSR security helpers
|
|
2
|
+
// Shared by runtime render paths to keep URL-attribute handling consistent.
|
|
3
|
+
|
|
4
|
+
const URL_ATTRS = new Set([
|
|
5
|
+
'href',
|
|
6
|
+
'src',
|
|
7
|
+
'action',
|
|
8
|
+
'formaction',
|
|
9
|
+
'poster',
|
|
10
|
+
'cite',
|
|
11
|
+
'background',
|
|
12
|
+
'xlink:href',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const URL_LIST_ATTRS = new Set(['srcset']);
|
|
16
|
+
|
|
17
|
+
function normalizeAttrName(name) {
|
|
18
|
+
return String(name).toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeUrlForProtocolCheck(url) {
|
|
22
|
+
return String(url).trim().replace(/[\s\x00-\x1f\x7f]/g, '').toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isSafeUrlValue(value) {
|
|
26
|
+
if (typeof value !== 'string') return true;
|
|
27
|
+
const normalized = normalizeUrlForProtocolCheck(value);
|
|
28
|
+
return !(
|
|
29
|
+
normalized.startsWith('javascript:') ||
|
|
30
|
+
normalized.startsWith('data:') ||
|
|
31
|
+
normalized.startsWith('vbscript:')
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isSafeSrcsetValue(value) {
|
|
36
|
+
if (typeof value !== 'string') return true;
|
|
37
|
+
return value
|
|
38
|
+
.split(',')
|
|
39
|
+
.every(candidate => {
|
|
40
|
+
const url = candidate.trim().split(/\s+/, 1)[0] || '';
|
|
41
|
+
return url === '' || isSafeUrlValue(url);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isUrlAttribute(name) {
|
|
46
|
+
return URL_ATTRS.has(normalizeAttrName(name));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isUrlListAttribute(name) {
|
|
50
|
+
return URL_LIST_ATTRS.has(normalizeAttrName(name));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isSafeUrlAttributeValue(name, value) {
|
|
54
|
+
if (isUrlListAttribute(name)) return isSafeSrcsetValue(value);
|
|
55
|
+
if (isUrlAttribute(name)) return isSafeUrlValue(value);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getDomAttributeName(name) {
|
|
60
|
+
if (name === 'className') return 'class';
|
|
61
|
+
if (name === 'htmlFor') return 'for';
|
|
62
|
+
return normalizeAttrName(name) === 'formaction' ? 'formaction' : name;
|
|
63
|
+
}
|