@atlassian/chippy 0.0.0-stage → 0.0.1-beta.1
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/LICENSE +201 -0
- package/README.md +188 -2
- package/bin/chippy.mjs +134 -0
- package/bin/pack.mjs +184 -0
- package/lib/avalanche.js +2570 -0
- package/lib/avalanche.js.map +7 -0
- package/lib/bootstrap.d.ts +1 -0
- package/lib/bootstrap.js +51 -0
- package/lib/bootstrap.js.map +7 -0
- package/lib/browser.js +15 -0
- package/lib/browser.js.map +7 -0
- package/lib/extension/avalanche.es.js +5396 -0
- package/lib/extension/avalanche.es.js.map +1 -0
- package/lib/extension/chippy-page-bridge.js +1833 -0
- package/lib/extension/inspect-react.js +216 -0
- package/lib/extension/layout-lab.js +1453 -0
- package/lib/extension/panel-dom.js +90 -0
- package/lib/extension/resolve-probes.js +342 -0
- package/lib/mcp-skill/LICENSE +201 -0
- package/lib/mcp-skill/README.md +150 -0
- package/lib/mcp-skill/SKILL.md +330 -0
- package/lib/mcp-skill/browser-scripts/playwright-probes.js +368 -0
- package/lib/mcp-skill/browser-scripts/playwright-session.js +114 -0
- package/lib/mcp-skill/extension/avalanche.es.js +5396 -0
- package/lib/mcp-skill/extension/avalanche.es.js.map +1 -0
- package/lib/mcp-skill/extension/avalanche.iife.js +5397 -0
- package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -0
- package/lib/mcp-skill/extension/background.js +323 -0
- package/lib/mcp-skill/extension/chippy-page-bridge.js +1833 -0
- package/lib/mcp-skill/extension/content-script.js +167 -0
- package/lib/mcp-skill/extension/devtools-hook-shim.js +68 -0
- package/lib/mcp-skill/extension/early-bootstrap.js +47 -0
- package/lib/mcp-skill/extension/icon-128.png +0 -0
- package/lib/mcp-skill/extension/icon-16.png +0 -0
- package/lib/mcp-skill/extension/icon-32.png +0 -0
- package/lib/mcp-skill/extension/icon-48.png +0 -0
- package/lib/mcp-skill/extension/inspect-react.js +216 -0
- package/lib/mcp-skill/extension/layout-lab.js +1453 -0
- package/lib/mcp-skill/extension/logo.svg +14 -0
- package/lib/mcp-skill/extension/manifest.json +69 -0
- package/lib/mcp-skill/extension/panel-dom.js +90 -0
- package/lib/mcp-skill/extension/panel-settings.js +191 -0
- package/lib/mcp-skill/extension/popup.html +41 -0
- package/lib/mcp-skill/extension/popup.js +80 -0
- package/lib/mcp-skill/extension/probe-descriptor.js +241 -0
- package/lib/mcp-skill/extension/resolve-probes.js +342 -0
- package/lib/mcp-skill/package.json +1 -0
- package/lib/mcp-skill/references/README.md +56 -0
- package/lib/mcp-skill/references/active-stimulus.md +85 -0
- package/lib/mcp-skill/references/avalanche-detector.md +731 -0
- package/lib/mcp-skill/references/bdd-router.md +310 -0
- package/lib/mcp-skill/references/browser-runtime-api.md +193 -0
- package/lib/mcp-skill/references/command-output-formats.md +1075 -0
- package/lib/mcp-skill/references/command-surface.md +183 -0
- package/lib/mcp-skill/references/cynefin.md +99 -0
- package/lib/mcp-skill/references/debug-hook.md +161 -0
- package/lib/mcp-skill/references/fixing-performance.md +490 -0
- package/lib/mcp-skill/references/hook-metrics.md +199 -0
- package/lib/mcp-skill/references/hooks-inspection.md +61 -0
- package/lib/mcp-skill/references/jira-taxonomy.md +81 -0
- package/lib/mcp-skill/references/mcp.md +122 -0
- package/lib/mcp-skill/references/mode-a-injection.md +68 -0
- package/lib/mcp-skill/references/operating-model.md +128 -0
- package/lib/mcp-skill/references/playwright-cli-setup.md +105 -0
- package/lib/mcp-skill/references/playwright-probes.md +205 -0
- package/lib/mcp-skill/references/procedures.md +545 -0
- package/lib/mcp-skill/references/recipes.txt +114 -0
- package/lib/mcp-skill/references/stability-render.md +428 -0
- package/lib/mcp-skill/references/test-mode-playbook.md +193 -0
- package/lib/mcp-skill/references/troubleshooting.md +54 -0
- package/lib/mcp-skill/scripts/avalanche-analyze.cjs +495 -0
- package/lib/mcp-skill/scripts/build-avalanche-command.js +34 -0
- package/lib/mcp-skill/scripts/build-fiber-state-command.js +19 -0
- package/lib/mcp-skill/scripts/chippy-helper-server.js +1710 -0
- package/lib/mcp-skill/scripts/chippy-mcp.cjs +273 -0
- package/lib/mcp-skill/scripts/chippy.sh +1218 -0
- package/lib/mcp-skill/scripts/format-probe-result.js +34 -0
- package/lib/mcp-skill/scripts/helper-access.js +144 -0
- package/lib/mcp-skill/scripts/probe-annotation-channel.js +116 -0
- package/lib/mcp-skill/scripts/search-recipes.js +248 -0
- package/lib/mcp-skill/snippets/README.md +89 -0
- package/lib/mcp-skill/snippets/count-context-consumers.js +40 -0
- package/lib/mcp-skill/snippets/find-context-providers.js +38 -0
- package/lib/mcp-skill/snippets/find-legacy-context.js +47 -0
- package/lib/mcp-skill/snippets/find-prop.js +83 -0
- package/lib/mcp-skill/snippets/hook-metrics-sample.js +69 -0
- package/lib/mcp-skill/snippets/hook-metrics.js +60 -0
- package/lib/mcp-skill/snippets/hook-tracker.js +198 -0
- package/lib/mcp-skill/snippets/stability-nudge.js +101 -0
- package/lib/package.json +1 -0
- package/lib/plugin.js +119 -0
- package/lib/plugin.js.map +7 -0
- package/lib/storybook/avalanche-panel-model.js +79 -0
- package/lib/storybook/brand.jsx +6 -0
- package/lib/storybook/connection-fallback.jsx +47 -0
- package/lib/storybook/constants.js +15 -0
- package/lib/storybook/context-card.jsx +171 -0
- package/lib/storybook/manager.jsx +403 -0
- package/lib/storybook/preset.js +7 -0
- package/lib/storybook/preview.js +263 -0
- package/lib/storybook/probe-annotation-editor.jsx +52 -0
- package/lib/storybook/probe-intent.js +23 -0
- package/lib/storybook/setup.js +93 -0
- package/lib/types/avalanche/commit-metrics-adapter.d.ts +76 -0
- package/lib/types/avalanche/commit-stats-indices.d.ts +41 -0
- package/lib/types/avalanche/commit-surface-ownership.d.ts +9 -0
- package/lib/types/avalanche/detector.d.ts +183 -0
- package/lib/types/avalanche/interaction-capture.d.ts +25 -0
- package/lib/types/avalanche/internals.d.ts +189 -0
- package/lib/types/avalanche/report-serializer.d.ts +283 -0
- package/lib/types/avalanche/reporter.d.ts +610 -0
- package/lib/types/chippy-bridge/fiber-source.d.ts +37 -0
- package/lib/types/chippy-bridge/react-renderer.d.ts +49 -0
- package/lib/types/chippy-bridge/source-map.d.ts +30 -0
- package/lib/types/debug-render/core.d.ts +72 -0
- package/lib/types/layout-lab/fiber-boundaries.d.ts +51 -0
- package/lib/types/layout-lab/fiber-walk.d.ts +131 -0
- package/lib/types/layout-lab/marker-context.d.ts +94 -0
- package/lib/types/package/avalanche.d.ts +6 -0
- package/lib/types/package/browser.d.ts +10 -0
- package/lib/types/package/plugin.d.ts +15 -0
- package/lib/types/plugins/atl/commit-metrics-adapter.d.ts +65 -0
- package/lib/types/plugins/atl/metrics-slot.d.ts +37 -0
- package/lib/types/plugins/index.d.ts +49 -0
- package/lib/types/plugins/jira/context-provider.d.ts +36 -0
- package/package.json +104 -4
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="chippy-trace-blue" x1="4" y1="25" x2="28" y2="7" gradientUnits="userSpaceOnUse"><stop stop-color="#1558BC"/><stop offset="1" stop-color="#357DE8"/></linearGradient>
|
|
4
|
+
<linearGradient id="chippy-trace-light" x1="4" y1="9" x2="27" y2="9" gradientUnits="userSpaceOnUse"><stop stop-color="#85B8FF"/><stop offset="1" stop-color="#357DE8"/></linearGradient>
|
|
5
|
+
</defs>
|
|
6
|
+
<style>
|
|
7
|
+
@keyframes chippy-trace-reveal { from { opacity: .45; transform: translateY(-1.5px); } to { opacity: 1; transform: translateY(0); } }
|
|
8
|
+
.chippy-trace-cut { animation: chippy-trace-reveal 650ms ease-out both; }
|
|
9
|
+
@media (prefers-reduced-motion: reduce) { .chippy-trace-cut { animation: none; } }
|
|
10
|
+
</style>
|
|
11
|
+
<path d="M4 9 16 1.5 28 9 21 13.4 16 10.3 11 13.4v5.2l5 3.1 5-3.1 7 4.4L16 30.5 4 23Z" fill="url(#chippy-trace-blue)"/>
|
|
12
|
+
<path class="chippy-trace-cut" d="M4 9 16 1.5 28 9 21 13.4 16 10.3 11 13.4Z" fill="url(#chippy-trace-light)"/>
|
|
13
|
+
<path d="m11 18.6 5 3.1 5-3.1 7 4.4-12 7.5-5-3.1Z" fill="#1868DB"/>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Chippy Probe Bridge",
|
|
4
|
+
"version": "0.4.24",
|
|
5
|
+
"description": "Inspects connected React pages to trace components, boundaries, hooks, props, and render behavior.",
|
|
6
|
+
"permissions": [
|
|
7
|
+
"storage",
|
|
8
|
+
"scripting",
|
|
9
|
+
"webNavigation"
|
|
10
|
+
],
|
|
11
|
+
"host_permissions": [
|
|
12
|
+
"http://*/*",
|
|
13
|
+
"https://*/*"
|
|
14
|
+
],
|
|
15
|
+
"background": {
|
|
16
|
+
"service_worker": "background.js"
|
|
17
|
+
},
|
|
18
|
+
"action": {
|
|
19
|
+
"default_title": "Chippy Probe Bridge",
|
|
20
|
+
"default_popup": "popup.html",
|
|
21
|
+
"default_icon": {
|
|
22
|
+
"16": "icon-16.png",
|
|
23
|
+
"32": "icon-32.png",
|
|
24
|
+
"48": "icon-48.png",
|
|
25
|
+
"128": "icon-128.png"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"content_scripts": [
|
|
29
|
+
{
|
|
30
|
+
"matches": [
|
|
31
|
+
"http://*/*",
|
|
32
|
+
"https://*/*"
|
|
33
|
+
],
|
|
34
|
+
"js": [
|
|
35
|
+
"panel-settings.js",
|
|
36
|
+
"content-script.js"
|
|
37
|
+
],
|
|
38
|
+
"run_at": "document_start",
|
|
39
|
+
"all_frames": true
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"web_accessible_resources": [
|
|
43
|
+
{
|
|
44
|
+
"resources": [
|
|
45
|
+
"devtools-hook-shim.js",
|
|
46
|
+
"early-bootstrap.js",
|
|
47
|
+
"inspect-react.js",
|
|
48
|
+
"panel-dom.js",
|
|
49
|
+
"layout-lab.js",
|
|
50
|
+
"resolve-probes.js",
|
|
51
|
+
"probe-descriptor.js",
|
|
52
|
+
"chippy-page-bridge.js",
|
|
53
|
+
"avalanche.iife.js",
|
|
54
|
+
"avalanche.iife.js.map",
|
|
55
|
+
"avalanche.es.js.map"
|
|
56
|
+
],
|
|
57
|
+
"matches": [
|
|
58
|
+
"http://*/*",
|
|
59
|
+
"https://*/*"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"icons": {
|
|
64
|
+
"16": "icon-16.png",
|
|
65
|
+
"32": "icon-32.png",
|
|
66
|
+
"48": "icon-48.png",
|
|
67
|
+
"128": "icon-128.png"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
(function installChippyPanelDom(global) {
|
|
3
|
+
function nodeKey(node) {
|
|
4
|
+
return node?.nodeType === 1 ? node.getAttribute('data-chippy-key') : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function canPatch(current, next) {
|
|
8
|
+
if (!current || !next || current.nodeType !== next.nodeType) return false;
|
|
9
|
+
if (current.nodeType === 1 && current.tagName !== next.tagName) return false;
|
|
10
|
+
const currentKey = nodeKey(current);
|
|
11
|
+
const nextKey = nodeKey(next);
|
|
12
|
+
return currentKey || nextKey ? currentKey === nextKey : true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function syncAttributes(current, next) {
|
|
16
|
+
Array.from(current.attributes).forEach((attribute) => {
|
|
17
|
+
if (!next.hasAttribute(attribute.name)) current.removeAttribute(attribute.name);
|
|
18
|
+
});
|
|
19
|
+
Array.from(next.attributes).forEach((attribute) => {
|
|
20
|
+
if (current.getAttribute(attribute.name) !== attribute.value) {
|
|
21
|
+
current.setAttribute(attribute.name, attribute.value);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function patchNode(current, next) {
|
|
27
|
+
if (current.nodeType === 3 || current.nodeType === 8) {
|
|
28
|
+
if (current.data !== next.data) current.data = next.data;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (current.nodeType !== 1) return;
|
|
32
|
+
|
|
33
|
+
const tagName = current.tagName;
|
|
34
|
+
const preservesValue = tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT';
|
|
35
|
+
const value = preservesValue ? current.value : null;
|
|
36
|
+
const checked = tagName === 'INPUT' ? current.checked : null;
|
|
37
|
+
|
|
38
|
+
syncAttributes(current, next);
|
|
39
|
+
patchChildNodes(current, next);
|
|
40
|
+
|
|
41
|
+
// Probe annotations are drafts until Attach is clicked. Rendering new
|
|
42
|
+
// runtime evidence must not overwrite an in-progress value.
|
|
43
|
+
if (preservesValue) current.value = value;
|
|
44
|
+
if (tagName === 'INPUT') current.checked = checked;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function patchChildNodes(currentParent, nextParent) {
|
|
48
|
+
const currentChildren = Array.from(currentParent.childNodes);
|
|
49
|
+
const keyed = new Map(
|
|
50
|
+
currentChildren
|
|
51
|
+
.map((node) => [nodeKey(node), node])
|
|
52
|
+
.filter(([key]) => Boolean(key)),
|
|
53
|
+
);
|
|
54
|
+
const used = new Set();
|
|
55
|
+
let cursor = currentParent.firstChild;
|
|
56
|
+
|
|
57
|
+
Array.from(nextParent.childNodes).forEach((nextChild) => {
|
|
58
|
+
const key = nodeKey(nextChild);
|
|
59
|
+
let current = key ? keyed.get(key) : null;
|
|
60
|
+
|
|
61
|
+
while (cursor && used.has(cursor)) cursor = cursor.nextSibling;
|
|
62
|
+
if (!current && cursor && !nodeKey(cursor) && canPatch(cursor, nextChild)) current = cursor;
|
|
63
|
+
|
|
64
|
+
if (current && canPatch(current, nextChild)) {
|
|
65
|
+
if (current !== cursor) currentParent.insertBefore(current, cursor);
|
|
66
|
+
patchNode(current, nextChild);
|
|
67
|
+
} else {
|
|
68
|
+
current = nextChild.cloneNode(true);
|
|
69
|
+
currentParent.insertBefore(current, cursor);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
used.add(current);
|
|
73
|
+
cursor = current.nextSibling;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
Array.from(currentParent.childNodes).forEach((child) => {
|
|
77
|
+
if (used.has(child)) return;
|
|
78
|
+
if (child.nodeType === 1 && child.hasAttribute('data-chippy-preserve')) return;
|
|
79
|
+
child.remove();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function patchChildren(target, html) {
|
|
84
|
+
const template = target.ownerDocument.createElement('template');
|
|
85
|
+
template.innerHTML = html;
|
|
86
|
+
patchChildNodes(target, template.content);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
global.__CHIPPY_PANEL_DOM__ = { patchChildren };
|
|
90
|
+
})(window);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Runs in Chrome's isolated content-script world. The content script owns one
|
|
3
|
+
// stable host + closed shadow root and portals it into the MAIN-world panel.
|
|
4
|
+
(function installChippyPanelSettings() {
|
|
5
|
+
try { globalThis.__CHIPPY_PANEL_SETTINGS_RUNTIME__?.destroy?.(); } catch {}
|
|
6
|
+
|
|
7
|
+
const DEFAULT_HELPER = 'http://127.0.0.1:7823';
|
|
8
|
+
// A packed build (`chippy pack`) prepends globalThis.__CHIPPY_BUILD to this
|
|
9
|
+
// file with its plugin list and the plugins it enables by default.
|
|
10
|
+
const BUILD = globalThis.__CHIPPY_BUILD || {};
|
|
11
|
+
const PLUGIN_OPTIONS = Array.isArray(BUILD.plugins) ? BUILD.plugins : [
|
|
12
|
+
{ id: 'jira', label: 'Jira ownership markers' },
|
|
13
|
+
{ id: 'atl', label: 'ATL commit metrics' },
|
|
14
|
+
];
|
|
15
|
+
const DEFAULT_PLUGINS = Array.isArray(BUILD.defaultPlugins) ? BUILD.defaultPlugins : [];
|
|
16
|
+
const SETTINGS_SLOT_SELECTOR = '[data-chippy-settings-slot]';
|
|
17
|
+
let observer = null;
|
|
18
|
+
let waitingForDom = false;
|
|
19
|
+
|
|
20
|
+
function normalizeBaseUrl(value) {
|
|
21
|
+
return String(value || DEFAULT_HELPER).trim().replace(/\/+$/, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateHelperUrl(value) {
|
|
25
|
+
try {
|
|
26
|
+
const url = new URL(value);
|
|
27
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseForwardPort(value) {
|
|
34
|
+
if (value === '') return null;
|
|
35
|
+
const port = Number(value);
|
|
36
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function escapeHtml(value) {
|
|
40
|
+
return String(value).replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function pluginMarkup() {
|
|
44
|
+
if (PLUGIN_OPTIONS.length === 0) return '';
|
|
45
|
+
const rows = PLUGIN_OPTIONS.map((plugin) =>
|
|
46
|
+
`<label><input data-plugin type="checkbox" value="${escapeHtml(plugin.id)}" />${escapeHtml(plugin.label || plugin.id)}</label>`);
|
|
47
|
+
return `<fieldset>
|
|
48
|
+
<legend>Plugins · apply on reload</legend>
|
|
49
|
+
${rows.join('\n ')}
|
|
50
|
+
</fieldset>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function settingsMarkup() {
|
|
54
|
+
return `
|
|
55
|
+
<style>
|
|
56
|
+
:host { all: initial; display: block; color-scheme: dark; }
|
|
57
|
+
* { box-sizing: border-box; }
|
|
58
|
+
form { display: grid; gap: 8px; color: #CECFD2; font: 11px/1.4 'Atlassian Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
59
|
+
label { display: grid; gap: 3px; }
|
|
60
|
+
label span { color: #A9ABAF; font-size: 9px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
|
|
61
|
+
input { width: 100%; border: 1px solid rgba(148,163,184,.25); border-radius: 7px; padding: 6px 8px; background: #1F1F21; color: #CECFD2; font: inherit; }
|
|
62
|
+
input:focus { outline: 2px solid #669DF1; outline-offset: 1px; }
|
|
63
|
+
fieldset { display: grid; gap: 4px; margin: 0; padding: 0; border: 0; }
|
|
64
|
+
legend { margin-bottom: 3px; padding: 0; color: #A9ABAF; font-size: 9px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
|
|
65
|
+
fieldset label { display: flex; align-items: center; gap: 6px; }
|
|
66
|
+
fieldset input { width: auto; margin: 0; }
|
|
67
|
+
.actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
68
|
+
button { border: 1px solid rgba(96,165,250,.35); border-radius: 7px; padding: 5px 10px; background: rgba(59,130,246,.2); color: #B8D6FF; font: inherit; font-weight: 700; cursor: pointer; }
|
|
69
|
+
[data-status] { min-height: 15px; color: #86efac; font-size: 10px; text-align: right; }
|
|
70
|
+
[data-status][data-error="true"] { color: #FD9891; }
|
|
71
|
+
</style>
|
|
72
|
+
<form>
|
|
73
|
+
<label>
|
|
74
|
+
<span>Chippy URL</span>
|
|
75
|
+
<input data-helper-url type="url" inputmode="url" required placeholder="${DEFAULT_HELPER}" />
|
|
76
|
+
</label>
|
|
77
|
+
<label>
|
|
78
|
+
<span>Forward to port · optional</span>
|
|
79
|
+
<input data-forward-port type="number" inputmode="numeric" min="1" max="65535" step="1" placeholder="Auto-detect from page URL" />
|
|
80
|
+
</label>
|
|
81
|
+
${pluginMarkup()}
|
|
82
|
+
<div class="actions">
|
|
83
|
+
<button type="submit">Save settings</button>
|
|
84
|
+
<div data-status role="status" aria-live="polite"></div>
|
|
85
|
+
</div>
|
|
86
|
+
</form>
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const host = document.createElement('div');
|
|
91
|
+
host.setAttribute('data-chippy-settings-host', 'true');
|
|
92
|
+
host.setAttribute('data-chippy-preserve', 'true');
|
|
93
|
+
const pluginRowsHeight = PLUGIN_OPTIONS.length ? 18 + 19 * PLUGIN_OPTIONS.length : 0;
|
|
94
|
+
host.style.cssText = `display:block;min-height:${176 + pluginRowsHeight}px;`;
|
|
95
|
+
const root = host.attachShadow({ mode: 'closed' });
|
|
96
|
+
const surface = document.createElement('div');
|
|
97
|
+
surface.innerHTML = settingsMarkup();
|
|
98
|
+
root.appendChild(surface);
|
|
99
|
+
|
|
100
|
+
const form = root.querySelector('form');
|
|
101
|
+
const helperInput = root.querySelector('[data-helper-url]');
|
|
102
|
+
const forwardPortInput = root.querySelector('[data-forward-port]');
|
|
103
|
+
const pluginInputs = Array.from(root.querySelectorAll('[data-plugin]'));
|
|
104
|
+
const statusNode = root.querySelector('[data-status]');
|
|
105
|
+
|
|
106
|
+
function setStatus(message, isError = false) {
|
|
107
|
+
statusNode.textContent = message;
|
|
108
|
+
statusNode.setAttribute('data-error', isError ? 'true' : 'false');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
chrome.storage.local.get({
|
|
112
|
+
helperBaseUrl: DEFAULT_HELPER,
|
|
113
|
+
forwardPort: null,
|
|
114
|
+
plugins: DEFAULT_PLUGINS,
|
|
115
|
+
}).then((stored) => {
|
|
116
|
+
helperInput.value = normalizeBaseUrl(stored.helperBaseUrl);
|
|
117
|
+
forwardPortInput.value = stored.forwardPort || '';
|
|
118
|
+
const plugins = Array.isArray(stored.plugins) ? stored.plugins : [];
|
|
119
|
+
for (const input of pluginInputs) input.checked = plugins.includes(input.value);
|
|
120
|
+
}).catch((error) => setStatus(error.message || 'Could not load settings.', true));
|
|
121
|
+
|
|
122
|
+
form.addEventListener('submit', async (event) => {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
const helperBaseUrl = normalizeBaseUrl(helperInput.value);
|
|
125
|
+
const forwardPort = parseForwardPort(forwardPortInput.value);
|
|
126
|
+
if (!validateHelperUrl(helperBaseUrl)) {
|
|
127
|
+
setStatus('Use an http(s) URL.', true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (forwardPort === undefined) {
|
|
131
|
+
setStatus('Port must be 1–65535.', true);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const plugins = pluginInputs.filter((input) => input.checked).map((input) => input.value);
|
|
137
|
+
await chrome.storage.local.set({ helperBaseUrl, forwardPort, plugins });
|
|
138
|
+
document.dispatchEvent(new CustomEvent('chippy-ext-command', {
|
|
139
|
+
detail: { type: 'apply-config', helperBaseUrl, activated: true },
|
|
140
|
+
}));
|
|
141
|
+
setStatus('Saved.');
|
|
142
|
+
} catch (error) {
|
|
143
|
+
setStatus(error.message || 'Could not save settings.', true);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
function mountPanelSettings() {
|
|
148
|
+
const liveDocument = globalThis.document;
|
|
149
|
+
if (!liveDocument?.querySelectorAll) return;
|
|
150
|
+
const slots = liveDocument.querySelectorAll(SETTINGS_SLOT_SELECTOR);
|
|
151
|
+
const slot = slots[slots.length - 1] || null;
|
|
152
|
+
if (slot && host.parentNode !== slot) slot.appendChild(host);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function installMountObserver() {
|
|
156
|
+
mountPanelSettings();
|
|
157
|
+
if (!globalThis.document?.documentElement) {
|
|
158
|
+
if (!waitingForDom) {
|
|
159
|
+
waitingForDom = true;
|
|
160
|
+
globalThis.document?.addEventListener('DOMContentLoaded', onDomReady, { once: true });
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
observer?.disconnect();
|
|
165
|
+
observer = new MutationObserver(mountPanelSettings);
|
|
166
|
+
observer.observe(globalThis.document.documentElement, { childList: true, subtree: true });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function onDomReady() {
|
|
170
|
+
waitingForDom = false;
|
|
171
|
+
installMountObserver();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function onMountRequest() {
|
|
175
|
+
mountPanelSettings();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function destroy() {
|
|
179
|
+
observer?.disconnect();
|
|
180
|
+
globalThis.document?.removeEventListener('DOMContentLoaded', onDomReady);
|
|
181
|
+
globalThis.document?.removeEventListener('chippy-settings-mount-request', onMountRequest);
|
|
182
|
+
host.remove();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
globalThis.__CHIPPY_PANEL_SETTINGS_RUNTIME__ = {
|
|
186
|
+
destroy,
|
|
187
|
+
mount: mountPanelSettings,
|
|
188
|
+
};
|
|
189
|
+
document.addEventListener('chippy-settings-mount-request', onMountRequest);
|
|
190
|
+
installMountObserver();
|
|
191
|
+
})();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Chippy</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
body {
|
|
9
|
+
width: 224px;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 12px;
|
|
12
|
+
display: grid;
|
|
13
|
+
grid-template-columns: 1fr 1fr;
|
|
14
|
+
gap: 8px;
|
|
15
|
+
background: #fff;
|
|
16
|
+
color: #292A2E;
|
|
17
|
+
font: 13px 'Atlassian Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
18
|
+
}
|
|
19
|
+
button {
|
|
20
|
+
border: 1px solid #DDDEE1;
|
|
21
|
+
border-radius: 6px;
|
|
22
|
+
padding: 8px 10px;
|
|
23
|
+
background: #fff;
|
|
24
|
+
color: #292A2E;
|
|
25
|
+
font: inherit;
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
#btn-activate { background: #1868DB; border-color: #1868DB; color: #fff; }
|
|
30
|
+
header { grid-column: 1 / -1; display: flex; align-items: center; gap: 8px; margin-bottom: 4px; font-size: 16px; font-weight: 650; }
|
|
31
|
+
button:focus-visible { outline: 2px solid #1868DB; outline-offset: 2px; }
|
|
32
|
+
button:disabled { cursor: default; opacity: .4; }
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<header><img src="logo.svg" width="28" height="28" alt="" />Chippy</header>
|
|
37
|
+
<button id="btn-activate" type="button">Enable</button>
|
|
38
|
+
<button id="btn-deactivate" type="button">Disable</button>
|
|
39
|
+
<script src="popup.js"></script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
const DEFAULT_HELPER = 'http://127.0.0.1:7823';
|
|
3
|
+
|
|
4
|
+
const btnActivate = document.querySelector('#btn-activate');
|
|
5
|
+
const btnDeactivate = document.querySelector('#btn-deactivate');
|
|
6
|
+
|
|
7
|
+
async function getActiveTab() {
|
|
8
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
9
|
+
return tab || null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getOrigin(tab) {
|
|
13
|
+
try {
|
|
14
|
+
return new URL(tab?.url || '').origin;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function getStoredConfig() {
|
|
21
|
+
return chrome.storage.local.get({
|
|
22
|
+
helperBaseUrl: DEFAULT_HELPER,
|
|
23
|
+
enabledOrigins: {},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function setOriginEnabled(origin, enabled) {
|
|
28
|
+
const stored = await getStoredConfig();
|
|
29
|
+
const enabledOrigins = { ...(stored.enabledOrigins || {}) };
|
|
30
|
+
if (enabled) enabledOrigins[origin] = true;
|
|
31
|
+
else delete enabledOrigins[origin];
|
|
32
|
+
await chrome.storage.local.set({ enabledOrigins });
|
|
33
|
+
return stored;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function renderState() {
|
|
37
|
+
const tab = await getActiveTab();
|
|
38
|
+
const origin = getOrigin(tab);
|
|
39
|
+
if (!tab || !origin) {
|
|
40
|
+
btnActivate.disabled = true;
|
|
41
|
+
btnDeactivate.disabled = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const stored = await getStoredConfig();
|
|
46
|
+
const enabled = Boolean(stored.enabledOrigins?.[origin]);
|
|
47
|
+
btnActivate.disabled = enabled;
|
|
48
|
+
btnDeactivate.disabled = !enabled;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
btnActivate.addEventListener('click', async () => {
|
|
52
|
+
const tab = await getActiveTab();
|
|
53
|
+
const origin = getOrigin(tab);
|
|
54
|
+
if (!tab || !origin) return;
|
|
55
|
+
|
|
56
|
+
const stored = await setOriginEnabled(origin, true);
|
|
57
|
+
try {
|
|
58
|
+
await chrome.runtime.sendMessage({ type: 'chippy-ensure-content-script', tabId: tab.id });
|
|
59
|
+
await chrome.runtime.sendMessage({
|
|
60
|
+
type: 'chippy-inject-bundle',
|
|
61
|
+
tabId: tab.id,
|
|
62
|
+
helperBaseUrl: stored.helperBaseUrl || DEFAULT_HELPER,
|
|
63
|
+
});
|
|
64
|
+
} catch {}
|
|
65
|
+
window.close();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
btnDeactivate.addEventListener('click', async () => {
|
|
69
|
+
const tab = await getActiveTab();
|
|
70
|
+
const origin = getOrigin(tab);
|
|
71
|
+
if (!tab || !origin) return;
|
|
72
|
+
|
|
73
|
+
await setOriginEnabled(origin, false);
|
|
74
|
+
try {
|
|
75
|
+
await chrome.tabs.sendMessage(tab.id, { type: 'chippy-disable-now' });
|
|
76
|
+
} catch {}
|
|
77
|
+
window.close();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
renderState();
|