@buoy-gg/shared-ui 7.0.15 → 7.0.16
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/lib/commonjs/JsModal.web.js +1588 -0
- package/lib/commonjs/env/EnvironmentIndicator.web.js +114 -0
- package/lib/commonjs/env/EnvironmentSelectorInline.web.js +178 -0
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/icons/index.js +14 -0
- package/lib/commonjs/index.js +3 -3
- package/lib/commonjs/stores/ignoredPatternsCore.js +68 -0
- package/lib/commonjs/stores/ignoredPatternsStore.js +16 -52
- package/lib/commonjs/stores/ignoredPatternsStore.web.js +236 -0
- package/lib/commonjs/stores/index.js +1 -1
- package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +5 -3
- package/lib/module/JsModal.web.js +1586 -0
- package/lib/module/env/EnvironmentIndicator.web.js +108 -0
- package/lib/module/env/EnvironmentSelectorInline.web.js +173 -0
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/icons/index.js +1 -0
- package/lib/module/index.js +3 -3
- package/lib/module/stores/ignoredPatternsCore.js +62 -0
- package/lib/module/stores/ignoredPatternsStore.js +5 -51
- package/lib/module/stores/ignoredPatternsStore.web.js +221 -0
- package/lib/module/stores/index.js +1 -1
- package/lib/module/ui/gameUI/constants/gameUIColors.js +4 -2
- package/lib/typescript/commonjs/JsModal.web.d.ts +138 -0
- package/lib/typescript/commonjs/JsModal.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/env/EnvironmentIndicator.web.d.ts +16 -0
- package/lib/typescript/commonjs/env/EnvironmentIndicator.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/env/EnvironmentSelectorInline.web.d.ts +8 -0
- package/lib/typescript/commonjs/env/EnvironmentSelectorInline.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/icons/index.d.ts +1 -0
- package/lib/typescript/commonjs/icons/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/ignoredPatternsCore.d.ts +37 -0
- package/lib/typescript/commonjs/stores/ignoredPatternsCore.d.ts.map +1 -0
- package/lib/typescript/commonjs/stores/ignoredPatternsStore.d.ts +3 -27
- package/lib/typescript/commonjs/stores/ignoredPatternsStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/stores/ignoredPatternsStore.web.d.ts +72 -0
- package/lib/typescript/commonjs/stores/ignoredPatternsStore.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +56 -0
- package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
- package/lib/typescript/module/JsModal.web.d.ts +138 -0
- package/lib/typescript/module/JsModal.web.d.ts.map +1 -0
- package/lib/typescript/module/env/EnvironmentIndicator.web.d.ts +16 -0
- package/lib/typescript/module/env/EnvironmentIndicator.web.d.ts.map +1 -0
- package/lib/typescript/module/env/EnvironmentSelectorInline.web.d.ts +8 -0
- package/lib/typescript/module/env/EnvironmentSelectorInline.web.d.ts.map +1 -0
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/icons/index.d.ts +1 -0
- package/lib/typescript/module/icons/index.d.ts.map +1 -1
- package/lib/typescript/module/stores/ignoredPatternsCore.d.ts +37 -0
- package/lib/typescript/module/stores/ignoredPatternsCore.d.ts.map +1 -0
- package/lib/typescript/module/stores/ignoredPatternsStore.d.ts +3 -27
- package/lib/typescript/module/stores/ignoredPatternsStore.d.ts.map +1 -1
- package/lib/typescript/module/stores/ignoredPatternsStore.web.d.ts +72 -0
- package/lib/typescript/module/stores/ignoredPatternsStore.web.d.ts.map +1 -0
- package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +56 -0
- package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EnvironmentIndicator — Web (React DOM) twin of EnvironmentIndicator.tsx.
|
|
5
|
+
* Same env→color/label map and layout; lucide glyph icons are omitted from
|
|
6
|
+
* the badge on native too (dot + label only), so parity holds.
|
|
7
|
+
*/
|
|
8
|
+
import { gameUIColors } from "../ui/gameUI/constants/gameUIColors.js";
|
|
9
|
+
import { normalizeEnvironment } from "../types/types.js";
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
export function getEnvironmentConfig(environment) {
|
|
12
|
+
switch (normalizeEnvironment(String(environment))) {
|
|
13
|
+
case 'dev':
|
|
14
|
+
return {
|
|
15
|
+
label: 'DEV',
|
|
16
|
+
backgroundColor: gameUIColors.warning
|
|
17
|
+
};
|
|
18
|
+
case 'qa':
|
|
19
|
+
return {
|
|
20
|
+
label: 'QA',
|
|
21
|
+
backgroundColor: gameUIColors.optional
|
|
22
|
+
};
|
|
23
|
+
case 'staging':
|
|
24
|
+
return {
|
|
25
|
+
label: 'STAGING',
|
|
26
|
+
backgroundColor: gameUIColors.success
|
|
27
|
+
};
|
|
28
|
+
case 'prod':
|
|
29
|
+
return {
|
|
30
|
+
label: 'PROD',
|
|
31
|
+
backgroundColor: gameUIColors.error
|
|
32
|
+
};
|
|
33
|
+
case 'local':
|
|
34
|
+
default:
|
|
35
|
+
return {
|
|
36
|
+
label: 'LOCAL',
|
|
37
|
+
backgroundColor: gameUIColors.info
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function ChevronDownGlyph({
|
|
42
|
+
color
|
|
43
|
+
}) {
|
|
44
|
+
// native: ChevronDown size 12 from shared lucide icons
|
|
45
|
+
return /*#__PURE__*/_jsx("svg", {
|
|
46
|
+
width: 12,
|
|
47
|
+
height: 12,
|
|
48
|
+
viewBox: "0 0 24 24",
|
|
49
|
+
"aria-hidden": "true",
|
|
50
|
+
children: /*#__PURE__*/_jsx("path", {
|
|
51
|
+
d: "M6 9l6 6 6-6",
|
|
52
|
+
fill: "none",
|
|
53
|
+
stroke: color,
|
|
54
|
+
strokeWidth: "2",
|
|
55
|
+
strokeLinecap: "round",
|
|
56
|
+
strokeLinejoin: "round"
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function EnvironmentIndicator({
|
|
61
|
+
environment,
|
|
62
|
+
interactive = false,
|
|
63
|
+
onPress
|
|
64
|
+
}) {
|
|
65
|
+
const envConfig = getEnvironmentConfig(environment);
|
|
66
|
+
return /*#__PURE__*/_jsxs("button", {
|
|
67
|
+
onClick: interactive ? onPress : undefined,
|
|
68
|
+
"aria-label": `Environment: ${envConfig.label}`,
|
|
69
|
+
style: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexDirection: 'row',
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
// native: paddingVertical 6, paddingLeft 8, paddingRight interactive?4:8
|
|
74
|
+
padding: `6px ${interactive ? 4 : 8}px 6px 8px`,
|
|
75
|
+
flexShrink: 0,
|
|
76
|
+
background: 'none',
|
|
77
|
+
border: 'none',
|
|
78
|
+
cursor: interactive ? 'pointer' : 'default'
|
|
79
|
+
},
|
|
80
|
+
children: [/*#__PURE__*/_jsx("div", {
|
|
81
|
+
style: {
|
|
82
|
+
width: 8,
|
|
83
|
+
height: 8,
|
|
84
|
+
borderRadius: 4,
|
|
85
|
+
backgroundColor: envConfig.backgroundColor,
|
|
86
|
+
marginRight: 6,
|
|
87
|
+
// native: shadowOpacity 0.6, shadowRadius 4 glow
|
|
88
|
+
boxShadow: `0 0 4px ${envConfig.backgroundColor}99`
|
|
89
|
+
}
|
|
90
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
91
|
+
style: {
|
|
92
|
+
fontSize: 11,
|
|
93
|
+
fontWeight: 600,
|
|
94
|
+
color: gameUIColors.primaryLight,
|
|
95
|
+
letterSpacing: 0.5
|
|
96
|
+
},
|
|
97
|
+
children: envConfig.label
|
|
98
|
+
}), interactive && /*#__PURE__*/_jsx("span", {
|
|
99
|
+
style: {
|
|
100
|
+
marginLeft: 2,
|
|
101
|
+
display: 'flex'
|
|
102
|
+
},
|
|
103
|
+
children: /*#__PURE__*/_jsx(ChevronDownGlyph, {
|
|
104
|
+
color: "#7A8599"
|
|
105
|
+
})
|
|
106
|
+
})]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EnvironmentSelectorInline — Web (React DOM) twin of
|
|
5
|
+
* EnvironmentSelectorInline.tsx: the interactive environment badge that
|
|
6
|
+
* expands a drop-UP panel (absolute bottom:100%, opens upward so the bubble
|
|
7
|
+
* layout doesn't shift). Same geometry: OPTION_HEIGHT 36, 200ms animation,
|
|
8
|
+
* selected row gets a green check.
|
|
9
|
+
*/
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { gameUIColors } from "../ui/gameUI/constants/gameUIColors.js";
|
|
12
|
+
import { getEnvironmentConfig } from "./EnvironmentIndicator.web.js";
|
|
13
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
+
const OPTION_HEIGHT = 36; // native EnvironmentSelectorInline OPTION_HEIGHT
|
|
15
|
+
|
|
16
|
+
function ChevronUpGlyph({
|
|
17
|
+
color,
|
|
18
|
+
flipped
|
|
19
|
+
}) {
|
|
20
|
+
return /*#__PURE__*/_jsx("svg", {
|
|
21
|
+
width: 10,
|
|
22
|
+
height: 10,
|
|
23
|
+
viewBox: "0 0 24 24",
|
|
24
|
+
"aria-hidden": "true",
|
|
25
|
+
style: {
|
|
26
|
+
transform: flipped ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
27
|
+
transition: 'transform 200ms ease' // native: 200ms rotation
|
|
28
|
+
},
|
|
29
|
+
children: /*#__PURE__*/_jsx("path", {
|
|
30
|
+
d: "M18 15l-6-6-6 6",
|
|
31
|
+
fill: "none",
|
|
32
|
+
stroke: color,
|
|
33
|
+
strokeWidth: "2",
|
|
34
|
+
strokeLinecap: "round",
|
|
35
|
+
strokeLinejoin: "round"
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function CheckGlyph() {
|
|
40
|
+
// native: Check size 12 color #4AFF9F strokeWidth 3
|
|
41
|
+
return /*#__PURE__*/_jsx("svg", {
|
|
42
|
+
width: 12,
|
|
43
|
+
height: 12,
|
|
44
|
+
viewBox: "0 0 24 24",
|
|
45
|
+
"aria-hidden": "true",
|
|
46
|
+
children: /*#__PURE__*/_jsx("path", {
|
|
47
|
+
d: "M20 6L9 17l-5-5",
|
|
48
|
+
fill: "none",
|
|
49
|
+
stroke: "#4AFF9F",
|
|
50
|
+
strokeWidth: "3",
|
|
51
|
+
strokeLinecap: "round",
|
|
52
|
+
strokeLinejoin: "round"
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function EnvironmentSelectorInline({
|
|
57
|
+
currentEnvironment,
|
|
58
|
+
availableEnvironments,
|
|
59
|
+
onSelect
|
|
60
|
+
}) {
|
|
61
|
+
const [expanded, setExpanded] = useState(false);
|
|
62
|
+
const [hoveredEnv, setHoveredEnv] = useState(null);
|
|
63
|
+
const current = getEnvironmentConfig(currentEnvironment);
|
|
64
|
+
const panelHeight = availableEnvironments.length * OPTION_HEIGHT;
|
|
65
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
66
|
+
style: {
|
|
67
|
+
position: 'relative',
|
|
68
|
+
zIndex: 1000,
|
|
69
|
+
flexShrink: 0
|
|
70
|
+
},
|
|
71
|
+
children: [/*#__PURE__*/_jsx("div", {
|
|
72
|
+
style: {
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
bottom: '100%',
|
|
75
|
+
left: 0,
|
|
76
|
+
right: 0,
|
|
77
|
+
height: expanded ? panelHeight : 0,
|
|
78
|
+
opacity: expanded ? 1 : 0,
|
|
79
|
+
overflow: 'hidden',
|
|
80
|
+
transition: 'height 200ms ease, opacity 200ms ease',
|
|
81
|
+
backgroundColor: 'rgba(8,12,21,0.95)',
|
|
82
|
+
border: '1px solid #00B8E666',
|
|
83
|
+
borderBottomWidth: 0,
|
|
84
|
+
borderTopLeftRadius: 6,
|
|
85
|
+
borderTopRightRadius: 6,
|
|
86
|
+
boxShadow: '0 -4px 8px rgba(0,0,0,0.3)'
|
|
87
|
+
},
|
|
88
|
+
children: availableEnvironments.map(env => {
|
|
89
|
+
const config = getEnvironmentConfig(env);
|
|
90
|
+
const isSelected = String(env).toLowerCase() === String(currentEnvironment).toLowerCase();
|
|
91
|
+
return /*#__PURE__*/_jsxs("button", {
|
|
92
|
+
onClick: () => {
|
|
93
|
+
setExpanded(false);
|
|
94
|
+
onSelect(env);
|
|
95
|
+
},
|
|
96
|
+
onMouseEnter: () => setHoveredEnv(String(env)),
|
|
97
|
+
onMouseLeave: () => setHoveredEnv(null),
|
|
98
|
+
style: {
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'space-between',
|
|
102
|
+
width: '100%',
|
|
103
|
+
height: OPTION_HEIGHT,
|
|
104
|
+
padding: '0 10px',
|
|
105
|
+
background: isSelected ? 'rgba(128,128,128,0.1)' : hoveredEnv === String(env) ? 'rgba(128,128,128,0.2)' : 'none',
|
|
106
|
+
border: 'none',
|
|
107
|
+
cursor: 'pointer'
|
|
108
|
+
},
|
|
109
|
+
children: [/*#__PURE__*/_jsxs("span", {
|
|
110
|
+
style: {
|
|
111
|
+
display: 'flex',
|
|
112
|
+
alignItems: 'center'
|
|
113
|
+
},
|
|
114
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
115
|
+
style: {
|
|
116
|
+
width: 6,
|
|
117
|
+
height: 6,
|
|
118
|
+
borderRadius: 3,
|
|
119
|
+
backgroundColor: config.backgroundColor,
|
|
120
|
+
marginRight: 8
|
|
121
|
+
}
|
|
122
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
123
|
+
style: {
|
|
124
|
+
fontSize: 10,
|
|
125
|
+
fontWeight: 600,
|
|
126
|
+
color: gameUIColors.primaryLight,
|
|
127
|
+
letterSpacing: 0.5
|
|
128
|
+
},
|
|
129
|
+
children: config.label
|
|
130
|
+
})]
|
|
131
|
+
}), isSelected && /*#__PURE__*/_jsx(CheckGlyph, {})]
|
|
132
|
+
}, String(env));
|
|
133
|
+
})
|
|
134
|
+
}), /*#__PURE__*/_jsxs("button", {
|
|
135
|
+
onClick: () => setExpanded(e => !e),
|
|
136
|
+
"aria-expanded": expanded,
|
|
137
|
+
"aria-label": `Environment: ${current.label}. Click to switch.`,
|
|
138
|
+
style: {
|
|
139
|
+
display: 'flex',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
gap: 6,
|
|
142
|
+
padding: '6px 6px 6px 8px',
|
|
143
|
+
borderRadius: 6,
|
|
144
|
+
// native: expanded trigger fuses with panel (squared top corners)
|
|
145
|
+
borderTopLeftRadius: expanded ? 0 : 6,
|
|
146
|
+
borderTopRightRadius: expanded ? 0 : 6,
|
|
147
|
+
backgroundColor: expanded ? 'rgba(8,12,21,0.95)' : 'transparent',
|
|
148
|
+
border: 'none',
|
|
149
|
+
cursor: 'pointer'
|
|
150
|
+
},
|
|
151
|
+
children: [/*#__PURE__*/_jsx("span", {
|
|
152
|
+
style: {
|
|
153
|
+
width: 8,
|
|
154
|
+
height: 8,
|
|
155
|
+
borderRadius: 4,
|
|
156
|
+
backgroundColor: current.backgroundColor,
|
|
157
|
+
boxShadow: `0 0 4px ${current.backgroundColor}99`
|
|
158
|
+
}
|
|
159
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
160
|
+
style: {
|
|
161
|
+
fontSize: 11,
|
|
162
|
+
fontWeight: 600,
|
|
163
|
+
color: gameUIColors.primaryLight,
|
|
164
|
+
letterSpacing: 0.5
|
|
165
|
+
},
|
|
166
|
+
children: current.label
|
|
167
|
+
}), /*#__PURE__*/_jsx(ChevronUpGlyph, {
|
|
168
|
+
color: "#7A8599",
|
|
169
|
+
flipped: expanded
|
|
170
|
+
})]
|
|
171
|
+
})]
|
|
172
|
+
});
|
|
173
|
+
}
|
|
@@ -12,6 +12,7 @@ export { EnvIcon, ENV_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
|
|
12
12
|
// packages re-export from here rather than drawing their own copies.
|
|
13
13
|
export { ConsoleIcon, CONSOLE_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
|
14
14
|
export { ImagesIcon, IMAGES_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
|
15
|
+
export { AssetsIcon, ASSETS_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
|
15
16
|
export { ImpersonateIcon, IMPERSONATE_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
|
16
17
|
export { ImageOverlayIcon, IMAGE_OVERLAY_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
|
17
18
|
export { ZustandIcon, ZUSTAND_ICON_COLOR } from "@buoy-gg/floating-tools-core";
|
package/lib/module/index.js
CHANGED
|
@@ -53,7 +53,7 @@ export * from "./storage/index.js";
|
|
|
53
53
|
export { useSafeAreaInsets, useFilterManager, useDeviceInfo, getDeviceInfo, logDeviceInfo } from "./hooks/index.js";
|
|
54
54
|
|
|
55
55
|
// JsModal
|
|
56
|
-
export { JsModal } from "./JsModal
|
|
56
|
+
export { JsModal } from "./JsModal";
|
|
57
57
|
// game ui colors
|
|
58
58
|
export { gameUIColors, buoyColors } from "./ui/gameUI/constants/gameUIColors.js";
|
|
59
59
|
// Runtime re-tint hook — only the desktop dashboard's theme switcher uses it.
|
|
@@ -61,11 +61,11 @@ export { refreshGameUIColors } from "./ui/gameUI/constants/gameUIColors.js";
|
|
|
61
61
|
export { macOSColors } from "./ui/gameUI/constants/macOSDesignSystemColors.js";
|
|
62
62
|
export { dialColors } from "./ui/gameUI/constants/gameUIColors.js";
|
|
63
63
|
//EnvironmentIndicator
|
|
64
|
-
export { EnvironmentIndicator } from "./env/EnvironmentIndicator
|
|
64
|
+
export { EnvironmentIndicator } from "./env/EnvironmentIndicator";
|
|
65
65
|
// EnvironmentSelector
|
|
66
66
|
export { EnvironmentSelector } from "./env/EnvironmentSelector.js";
|
|
67
67
|
// EnvironmentSelectorInline
|
|
68
|
-
export { EnvironmentSelectorInline } from "./env/EnvironmentSelectorInline
|
|
68
|
+
export { EnvironmentSelectorInline } from "./env/EnvironmentSelectorInline";
|
|
69
69
|
export { normalizeEnvironment } from "./types/types.js";
|
|
70
70
|
|
|
71
71
|
// Context exports
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure, dependency-free part of the ignored-patterns feature — split from
|
|
5
|
+
* ignoredPatternsStore.ts so web consumers (which shim `@buoy-gg/shared-ui`
|
|
6
|
+
* and must not pull the store's relative `../utils/persistentStorage` import
|
|
7
|
+
* with its expo/async-storage requires into the bundle graph) can share the
|
|
8
|
+
* exact matching semantics. The store imports and re-exports everything here;
|
|
9
|
+
* the native barrel is unchanged.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How a pattern matches a URL:
|
|
14
|
+
*
|
|
15
|
+
* - `contains`: case-insensitive substring match on the full URL (legacy behavior).
|
|
16
|
+
* - `exact`: smart equality — if pattern starts with `/`, matches URL.pathname;
|
|
17
|
+
* if it starts with `http://`/`https://`, matches origin+pathname; otherwise
|
|
18
|
+
* matches URL.host.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** A single exclude pattern with its match mode. */
|
|
22
|
+
|
|
23
|
+
/** Patterns hidden by default — Buoy's own license API traffic. */
|
|
24
|
+
export const DEFAULT_IGNORED_PATTERNS = [{
|
|
25
|
+
value: "api.keygen.sh",
|
|
26
|
+
mode: "contains"
|
|
27
|
+
}];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns true when `url` matches the ignored `pattern` according to its mode.
|
|
31
|
+
*
|
|
32
|
+
* `contains` → case-insensitive substring on the full URL (legacy behavior).
|
|
33
|
+
* `exact` → smart equality: pattern starting with `/` compares URL.pathname,
|
|
34
|
+
* full URLs compare origin+pathname (ignoring query/hash), bare
|
|
35
|
+
* values compare URL.host. Falls back to literal equality if URL
|
|
36
|
+
* parsing fails (e.g. relative URLs).
|
|
37
|
+
*/
|
|
38
|
+
export function urlMatchesIgnoredPattern(url, pattern) {
|
|
39
|
+
const lowerUrl = url.toLowerCase();
|
|
40
|
+
const lowerValue = pattern.value.toLowerCase();
|
|
41
|
+
if (pattern.mode === "contains") {
|
|
42
|
+
return lowerUrl.includes(lowerValue);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
if (lowerValue.startsWith("/")) {
|
|
47
|
+
return parsed.pathname.toLowerCase() === lowerValue;
|
|
48
|
+
}
|
|
49
|
+
if (lowerValue.startsWith("http://") || lowerValue.startsWith("https://")) {
|
|
50
|
+
return `${parsed.origin}${parsed.pathname}`.toLowerCase() === lowerValue;
|
|
51
|
+
}
|
|
52
|
+
return parsed.host.toLowerCase() === lowerValue;
|
|
53
|
+
} catch {
|
|
54
|
+
return lowerUrl === lowerValue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Convenience: does `url` match ANY of the ignored `patterns`? */
|
|
59
|
+
export function isUrlIgnored(url, patterns) {
|
|
60
|
+
if (!url || patterns.length === 0) return false;
|
|
61
|
+
return patterns.some(pattern => urlMatchesIgnoredPattern(url, pattern));
|
|
62
|
+
}
|
|
@@ -17,57 +17,11 @@ import { useEffect, useMemo, useState } from "react";
|
|
|
17
17
|
import { persistentStorage } from "../utils/persistentStorage.js";
|
|
18
18
|
import { devToolsStorageKeys } from "../storage/devToolsStorageKeys.js";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* if it starts with `http://`/`https://`, matches origin+pathname; otherwise
|
|
26
|
-
* matches URL.host.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/** A single exclude pattern with its match mode. */
|
|
30
|
-
|
|
31
|
-
/** Patterns hidden by default — Buoy's own license API traffic. */
|
|
32
|
-
const DEFAULT_PATTERNS = [{
|
|
33
|
-
value: "api.keygen.sh",
|
|
34
|
-
mode: "contains"
|
|
35
|
-
}];
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Returns true when `url` matches the ignored `pattern` according to its mode.
|
|
39
|
-
*
|
|
40
|
-
* `contains` → case-insensitive substring on the full URL (legacy behavior).
|
|
41
|
-
* `exact` → smart equality: pattern starting with `/` compares URL.pathname,
|
|
42
|
-
* full URLs compare origin+pathname (ignoring query/hash), bare
|
|
43
|
-
* values compare URL.host. Falls back to literal equality if URL
|
|
44
|
-
* parsing fails (e.g. relative URLs).
|
|
45
|
-
*/
|
|
46
|
-
export function urlMatchesIgnoredPattern(url, pattern) {
|
|
47
|
-
const lowerUrl = url.toLowerCase();
|
|
48
|
-
const lowerValue = pattern.value.toLowerCase();
|
|
49
|
-
if (pattern.mode === "contains") {
|
|
50
|
-
return lowerUrl.includes(lowerValue);
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const parsed = new URL(url);
|
|
54
|
-
if (lowerValue.startsWith("/")) {
|
|
55
|
-
return parsed.pathname.toLowerCase() === lowerValue;
|
|
56
|
-
}
|
|
57
|
-
if (lowerValue.startsWith("http://") || lowerValue.startsWith("https://")) {
|
|
58
|
-
return `${parsed.origin}${parsed.pathname}`.toLowerCase() === lowerValue;
|
|
59
|
-
}
|
|
60
|
-
return parsed.host.toLowerCase() === lowerValue;
|
|
61
|
-
} catch {
|
|
62
|
-
return lowerUrl === lowerValue;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Convenience: does `url` match ANY of the ignored `patterns`? */
|
|
67
|
-
export function isUrlIgnored(url, patterns) {
|
|
68
|
-
if (!url || patterns.length === 0) return false;
|
|
69
|
-
return patterns.some(pattern => urlMatchesIgnoredPattern(url, pattern));
|
|
70
|
-
}
|
|
20
|
+
// Pure matching types/helpers live in ignoredPatternsCore.ts (web-shim-safe);
|
|
21
|
+
// re-exported here so the shared-ui barrel surface is unchanged.
|
|
22
|
+
import { DEFAULT_IGNORED_PATTERNS } from "./ignoredPatternsCore.js";
|
|
23
|
+
export { urlMatchesIgnoredPattern, isUrlIgnored } from "./ignoredPatternsCore.js";
|
|
24
|
+
const DEFAULT_PATTERNS = DEFAULT_IGNORED_PATTERNS;
|
|
71
25
|
|
|
72
26
|
/** Tolerate the legacy `string[]` persisted format and stray junk. */
|
|
73
27
|
function migratePatterns(raw) {
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ignoredPatternsStore.web — web twin of ignoredPatternsStore.ts (the shared
|
|
5
|
+
* persisted "ignored patterns" singleton for network-style URL filtering).
|
|
6
|
+
*
|
|
7
|
+
* The native store imports shared's RN `persistentStorage` (whose module graph
|
|
8
|
+
* carries guarded expo-file-system / async-storage requires), which is why the
|
|
9
|
+
* web shims never re-exported it. This twin keeps the ENTIRE store class, hook
|
|
10
|
+
* and persistence contract identical — same class shape, same
|
|
11
|
+
* `devToolsStorageKeys.network.ignoredDomains()` storage key, same persisted
|
|
12
|
+
* JSON shape (`IgnoredPattern[]`, with the legacy `string[]` migration) — and
|
|
13
|
+
* swaps only the storage backing: a file-local localStorage adapter that
|
|
14
|
+
* matches the `persistentStorage` API (async getItem/setItem, best-effort,
|
|
15
|
+
* provenance: example-tauri/src/shims/shared-ui.web.ts's adapter — web Buoy
|
|
16
|
+
* chrome persistence lives in webview localStorage).
|
|
17
|
+
*
|
|
18
|
+
* Everything else — pure matching (`ignoredPatternsCore`), the migration +
|
|
19
|
+
* ensure-defaults logic, the dirty-flag load race guard, the module-scope
|
|
20
|
+
* stable mutation identities — is the native code, not a fork of its
|
|
21
|
+
* semantics. Web consumers get the same shared, persisted pattern list the
|
|
22
|
+
* phone has, keyed identically, so a DOM port and a react-native-web port of
|
|
23
|
+
* the same app read/write the same entry.
|
|
24
|
+
*
|
|
25
|
+
* @platform React DOM Web
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { useEffect, useMemo, useState } from "react";
|
|
29
|
+
import { devToolsStorageKeys } from "../storage/devToolsStorageKeys.js";
|
|
30
|
+
|
|
31
|
+
// Pure matching types/helpers live in ignoredPatternsCore.ts (web-shim-safe);
|
|
32
|
+
// re-exported here so this module's surface twins the native store's.
|
|
33
|
+
import { DEFAULT_IGNORED_PATTERNS } from "./ignoredPatternsCore.js";
|
|
34
|
+
export { urlMatchesIgnoredPattern, isUrlIgnored } from "./ignoredPatternsCore.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* localStorage-backed implementation of the shared `persistentStorage` API
|
|
38
|
+
* (getItem/setItem returning promises, silent best-effort failure) — the same
|
|
39
|
+
* adapter recipe the web shared-ui shims use for Buoy chrome persistence.
|
|
40
|
+
* Local to this file so the web store pulls zero RN/expo modules.
|
|
41
|
+
*/
|
|
42
|
+
const persistentStorage = {
|
|
43
|
+
async getItem(key) {
|
|
44
|
+
try {
|
|
45
|
+
return window.localStorage.getItem(key);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
async setItem(key, value) {
|
|
51
|
+
try {
|
|
52
|
+
window.localStorage.setItem(key, value);
|
|
53
|
+
} catch {
|
|
54
|
+
// Ignore quota/availability errors — callers treat this as best-effort.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const DEFAULT_PATTERNS = DEFAULT_IGNORED_PATTERNS;
|
|
59
|
+
|
|
60
|
+
/** Tolerate the legacy `string[]` persisted format and stray junk. */
|
|
61
|
+
function migratePatterns(raw) {
|
|
62
|
+
if (!Array.isArray(raw)) return [];
|
|
63
|
+
const migrated = [];
|
|
64
|
+
for (const entry of raw) {
|
|
65
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
66
|
+
migrated.push({
|
|
67
|
+
value: entry,
|
|
68
|
+
mode: "contains"
|
|
69
|
+
});
|
|
70
|
+
} else if (entry && typeof entry === "object" && typeof entry.value === "string") {
|
|
71
|
+
const e = entry;
|
|
72
|
+
migrated.push({
|
|
73
|
+
value: e.value,
|
|
74
|
+
mode: e.mode === "exact" ? "exact" : "contains"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return migrated;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Always keep the default patterns present (e.g. Buoy's license API). */
|
|
82
|
+
function ensureDefaults(patterns) {
|
|
83
|
+
const result = [...patterns];
|
|
84
|
+
for (const def of DEFAULT_PATTERNS) {
|
|
85
|
+
if (!result.some(p => p.value === def.value)) {
|
|
86
|
+
result.push({
|
|
87
|
+
...def
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Singleton store. One instance per JS runtime, so every consumer (Network
|
|
95
|
+
* tool, Events tool, any web panel) shares the same patterns.
|
|
96
|
+
*/
|
|
97
|
+
class IgnoredPatternsStore {
|
|
98
|
+
patterns = ensureDefaults(DEFAULT_PATTERNS.map(p => ({
|
|
99
|
+
...p
|
|
100
|
+
})));
|
|
101
|
+
listeners = new Set();
|
|
102
|
+
loaded = false;
|
|
103
|
+
loadPromise = null;
|
|
104
|
+
/** Set once a mutation happens so a slow initial load can't clobber it. */
|
|
105
|
+
dirty = false;
|
|
106
|
+
|
|
107
|
+
/** Current patterns (stable reference until a mutation occurs). */
|
|
108
|
+
getPatterns() {
|
|
109
|
+
return this.patterns;
|
|
110
|
+
}
|
|
111
|
+
subscribe(listener) {
|
|
112
|
+
this.listeners.add(listener);
|
|
113
|
+
// Lazily hydrate from storage the first time anyone cares.
|
|
114
|
+
void this.ensureLoaded();
|
|
115
|
+
return () => {
|
|
116
|
+
this.listeners.delete(listener);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
emit() {
|
|
120
|
+
this.listeners.forEach(l => l());
|
|
121
|
+
}
|
|
122
|
+
ensureLoaded() {
|
|
123
|
+
if (this.loaded) return Promise.resolve();
|
|
124
|
+
if (this.loadPromise) return this.loadPromise;
|
|
125
|
+
this.loadPromise = (async () => {
|
|
126
|
+
try {
|
|
127
|
+
const stored = await persistentStorage.getItem(devToolsStorageKeys.network.ignoredDomains());
|
|
128
|
+
// A mutation may have raced this read — never overwrite user intent.
|
|
129
|
+
if (stored && !this.dirty) {
|
|
130
|
+
const migrated = ensureDefaults(migratePatterns(JSON.parse(stored)));
|
|
131
|
+
this.patterns = migrated;
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
// Silently fall back to defaults.
|
|
135
|
+
} finally {
|
|
136
|
+
this.loaded = true;
|
|
137
|
+
this.emit();
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
return this.loadPromise;
|
|
141
|
+
}
|
|
142
|
+
persist() {
|
|
143
|
+
persistentStorage.setItem(devToolsStorageKeys.network.ignoredDomains(), JSON.stringify(this.patterns)).catch(() => {
|
|
144
|
+
// Silently fail — patterns remain in memory.
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
commit(next) {
|
|
148
|
+
this.dirty = true;
|
|
149
|
+
this.patterns = next;
|
|
150
|
+
this.emit();
|
|
151
|
+
this.persist();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Add a pattern (no-op if its value already exists). */
|
|
155
|
+
add(pattern) {
|
|
156
|
+
const value = pattern.value.trim();
|
|
157
|
+
if (!value) return;
|
|
158
|
+
if (this.patterns.some(p => p.value === value)) return;
|
|
159
|
+
this.commit([...this.patterns, {
|
|
160
|
+
value,
|
|
161
|
+
mode: pattern.mode
|
|
162
|
+
}]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Remove a pattern by value. */
|
|
166
|
+
remove(value) {
|
|
167
|
+
if (!this.patterns.some(p => p.value === value)) return;
|
|
168
|
+
this.commit(this.patterns.filter(p => p.value !== value));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Add as `contains` if absent, otherwise remove (used by detail-view chips). */
|
|
172
|
+
toggle(value) {
|
|
173
|
+
const trimmed = value.trim();
|
|
174
|
+
if (!trimmed) return;
|
|
175
|
+
this.commit(this.patterns.some(p => p.value === trimmed) ? this.patterns.filter(p => p.value !== trimmed) : [...this.patterns, {
|
|
176
|
+
value: trimmed,
|
|
177
|
+
mode: "contains"
|
|
178
|
+
}]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Flip an existing pattern between `contains` and `exact`. */
|
|
182
|
+
toggleMode(value) {
|
|
183
|
+
if (!this.patterns.some(p => p.value === value)) return;
|
|
184
|
+
this.commit(this.patterns.map(p => p.value === value ? {
|
|
185
|
+
...p,
|
|
186
|
+
mode: p.mode === "contains" ? "exact" : "contains"
|
|
187
|
+
} : p));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export const ignoredPatternsStore = new IgnoredPatternsStore();
|
|
191
|
+
/**
|
|
192
|
+
* Mutations live at module scope so they keep a STABLE identity across
|
|
193
|
+
* renders (same rationale as native: inline arrows handed every consumer a
|
|
194
|
+
* fresh `toggle` per render and defeated `memo` downstream).
|
|
195
|
+
*/
|
|
196
|
+
const addPattern = pattern => ignoredPatternsStore.add(pattern);
|
|
197
|
+
const removePattern = value => ignoredPatternsStore.remove(value);
|
|
198
|
+
const togglePattern = value => ignoredPatternsStore.toggle(value);
|
|
199
|
+
const togglePatternMode = value => ignoredPatternsStore.toggleMode(value);
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Subscribe to the shared ignored-patterns store. All consumers in a runtime
|
|
203
|
+
* see the same patterns and the same mutations.
|
|
204
|
+
*/
|
|
205
|
+
export function useIgnoredPatterns() {
|
|
206
|
+
const [patterns, setPatterns] = useState(() => ignoredPatternsStore.getPatterns());
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
return ignoredPatternsStore.subscribe(() => {
|
|
209
|
+
setPatterns(ignoredPatternsStore.getPatterns());
|
|
210
|
+
});
|
|
211
|
+
}, []);
|
|
212
|
+
const values = useMemo(() => new Set(patterns.map(p => p.value)), [patterns]);
|
|
213
|
+
return {
|
|
214
|
+
patterns,
|
|
215
|
+
values,
|
|
216
|
+
add: addPattern,
|
|
217
|
+
remove: removePattern,
|
|
218
|
+
toggle: togglePattern,
|
|
219
|
+
toggleMode: togglePatternMode
|
|
220
|
+
};
|
|
221
|
+
}
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export { BaseEventStore } from "./BaseEventStore.js";
|
|
8
|
-
export { ignoredPatternsStore, useIgnoredPatterns, urlMatchesIgnoredPattern, isUrlIgnored } from "./ignoredPatternsStore
|
|
8
|
+
export { ignoredPatternsStore, useIgnoredPatterns, urlMatchesIgnoredPattern, isUrlIgnored } from "./ignoredPatternsStore";
|