@deeeed/metamask-harness 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +41 -0
  3. package/adapters/extension/build-lavamoat.sh +2 -1
  4. package/adapters/extension/ensure-browser.sh +82 -9
  5. package/adapters/extension/inject.mjs +1 -0
  6. package/adapters/extension/launch-browser.cjs +83 -1
  7. package/adapters/extension/lib/chrome-args.cjs +325 -1
  8. package/adapters/extension/lib/playwright-cdp.cjs +34 -0
  9. package/adapters/extension/lib/slot-title.cjs +2 -4
  10. package/adapters/extension/lib/validation-launch-supervisor.cjs +292 -0
  11. package/adapters/extension/lib/validation-process-ownership.cjs +69 -0
  12. package/adapters/extension/reattach.sh +2 -1
  13. package/adapters/extension/sidepanel-toggle.sh +14 -96
  14. package/adapters/extension/wallet-fixture-state.cjs +8 -31
  15. package/adapters/manifest.json +16 -0
  16. package/adapters/shared/private-atomic-write.cjs +47 -0
  17. package/adapters/shared/setup-base.sh +864 -0
  18. package/dist/adapters/extension/runtime.js +367 -24
  19. package/dist/adapters/extension/validation-process-ownership.js +10 -0
  20. package/dist/cli-commands.js +1 -0
  21. package/dist/command-contract.js +12 -0
  22. package/dist/commands/launch/extension.js +130 -19
  23. package/dist/commands/setup-base.js +24 -0
  24. package/dist/mm-harness-cli.js +28 -2
  25. package/library/actions/extension/analytics/consent.mjs +203 -0
  26. package/library/actions/extension/analytics/set_consent.mjs +19 -143
  27. package/library/actions/extension/perps/perps.mjs +2 -16
  28. package/library/actions/extension/perps/state.mjs +20 -0
  29. package/library/actions/extension/wallet/list_accounts.mjs +3 -25
  30. package/library/actions/extension/wallet/read_state.mjs +3 -23
  31. package/library/actions/extension/wallet/select_account.mjs +6 -33
  32. package/library/actions/extension/wallet/setup.mjs +2 -20
  33. package/library/actions/extension/wallet/state.mjs +111 -0
  34. package/library/recipes/runner/action-validation.extension.recipe.json +1 -1
  35. package/library/recipes/runner/action-validation.mobile.recipe.json +1 -1
  36. package/package.json +7 -4
  37. package/scripts/site-contrast.mjs +538 -0
  38. package/site/architecture.html +415 -0
  39. package/site/assets/progress.mjs +272 -0
  40. package/site/assets/style.css +808 -0
  41. package/site/cheatsheet.html +305 -0
  42. package/site/index.html +643 -0
  43. package/site/recipes.html +396 -0
  44. package/site/reviewers.html +374 -0
  45. package/site/tutorials/index.html +180 -0
  46. package/site/tutorials/v1.html +211 -0
  47. package/site/tutorials/v2.html +207 -0
  48. package/site/tutorials/v3.html +214 -0
  49. package/site/tutorials/v4.html +195 -0
  50. package/site/tutorials/v5.html +163 -0
  51. package/site/tutorials/v6.html +165 -0
  52. package/site/tutorials/v7.html +184 -0
@@ -0,0 +1,272 @@
1
+ /*
2
+ * Shared behaviour for the getting-started site: checklist progress, copy
3
+ * buttons, platform filters, and the layer diagram. No dependencies, no
4
+ * network, no tracking. Progress lives in localStorage under
5
+ * `mmh.progress.<page>`, where <page> comes from body[data-progress-page].
6
+ * Pages that share a namespace share their state (Start Here and the V1
7
+ * tutorial are deliberately the same checklist).
8
+ */
9
+ (function () {
10
+ 'use strict';
11
+
12
+ var KEY_PREFIX = 'mmh.progress.';
13
+
14
+ /* ---------- storage (degrades to in-memory if localStorage is blocked) ---------- */
15
+
16
+ var memory = {};
17
+
18
+ function read(key) {
19
+ try {
20
+ var raw = window.localStorage.getItem(key);
21
+ return raw ? JSON.parse(raw) : [];
22
+ } catch (_err) {
23
+ return memory[key] || [];
24
+ }
25
+ }
26
+
27
+ function write(key, ids) {
28
+ memory[key] = ids;
29
+ try {
30
+ window.localStorage.setItem(key, JSON.stringify(ids));
31
+ } catch (_err) {
32
+ /* Private mode or blocked storage: the page still works, just forgets. */
33
+ }
34
+ }
35
+
36
+ /* ---------- checklist ---------- */
37
+
38
+ function initChecklist() {
39
+ var page = document.body.getAttribute('data-progress-page');
40
+ var steps = [].slice.call(document.querySelectorAll('.step[data-step]'));
41
+ if (!page || !steps.length) return;
42
+
43
+ var key = KEY_PREFIX + page;
44
+ var fill = document.querySelector('.progress-fill');
45
+ var label = document.querySelector('.progress-label');
46
+ var reset = document.querySelector('.progress-reset');
47
+
48
+ function done() {
49
+ return steps.filter(function (s) { return s.getAttribute('data-done') === '1'; });
50
+ }
51
+
52
+ function render() {
53
+ var n = done().length;
54
+ var pct = steps.length ? Math.round((n / steps.length) * 100) : 0;
55
+ if (fill) {
56
+ fill.style.width = pct + '%';
57
+ fill.parentNode.setAttribute('aria-valuenow', String(pct));
58
+ }
59
+ if (label) {
60
+ label.textContent = n === steps.length
61
+ ? n + '/' + steps.length + ' — done'
62
+ : n + '/' + steps.length + ' steps';
63
+ }
64
+ }
65
+
66
+ function apply(ids) {
67
+ steps.forEach(function (step) {
68
+ var on = ids.indexOf(step.getAttribute('data-step')) !== -1;
69
+ step.setAttribute('data-done', on ? '1' : '0');
70
+ var box = step.querySelector('.step-check');
71
+ if (box) box.checked = on;
72
+ });
73
+ render();
74
+ }
75
+
76
+ steps.forEach(function (step) {
77
+ var box = step.querySelector('.step-check');
78
+ if (!box) return;
79
+ box.addEventListener('change', function () {
80
+ var ids = read(key);
81
+ var id = step.getAttribute('data-step');
82
+ var at = ids.indexOf(id);
83
+ if (box.checked && at === -1) ids.push(id);
84
+ if (!box.checked && at !== -1) ids.splice(at, 1);
85
+ write(key, ids);
86
+ apply(ids);
87
+ });
88
+ });
89
+
90
+ if (reset) {
91
+ reset.addEventListener('click', function () {
92
+ write(key, []);
93
+ apply([]);
94
+ });
95
+ }
96
+
97
+ /* Reflect edits made in another tab on the same namespace. */
98
+ window.addEventListener('storage', function (e) {
99
+ if (e.key === key) apply(read(key));
100
+ });
101
+
102
+ apply(read(key));
103
+ }
104
+
105
+ /* ---------- copy buttons ---------- */
106
+
107
+ function textOf(block) {
108
+ var pre = block.querySelector('pre');
109
+ if (!pre) return '';
110
+ var clone = pre.cloneNode(true);
111
+ /* Shell prompt markers are decoration, never part of the command. */
112
+ [].slice.call(clone.querySelectorAll('.p')).forEach(function (n) {
113
+ n.parentNode.removeChild(n);
114
+ });
115
+ return clone.textContent.replace(/[ \t]+$/gm, '').trim();
116
+ }
117
+
118
+ function legacyCopy(text) {
119
+ return new Promise(function (resolve, reject) {
120
+ var ta = document.createElement('textarea');
121
+ ta.value = text;
122
+ ta.setAttribute('readonly', '');
123
+ ta.style.position = 'fixed';
124
+ ta.style.opacity = '0';
125
+ document.body.appendChild(ta);
126
+ ta.select();
127
+ var ok = false;
128
+ try { ok = document.execCommand('copy'); } catch (_err) { ok = false; }
129
+ document.body.removeChild(ta);
130
+ ok ? resolve() : reject(new Error('copy unavailable'));
131
+ });
132
+ }
133
+
134
+ /*
135
+ * The async clipboard needs a secure context AND permission; it rejects when
136
+ * the document lacks focus. Fall through to the legacy path rather than
137
+ * telling the reader to give up.
138
+ */
139
+ function copy(text) {
140
+ if (navigator.clipboard && window.isSecureContext) {
141
+ return navigator.clipboard.writeText(text).catch(function () {
142
+ return legacyCopy(text);
143
+ });
144
+ }
145
+ return legacyCopy(text);
146
+ }
147
+
148
+ function initCopy() {
149
+ [].slice.call(document.querySelectorAll('.cmd')).forEach(function (block) {
150
+ if (block.querySelector('.copy')) return;
151
+ var btn = document.createElement('button');
152
+ btn.type = 'button';
153
+ btn.className = 'copy';
154
+ btn.textContent = 'Copy';
155
+ btn.setAttribute('aria-label', 'Copy to clipboard');
156
+ btn.addEventListener('click', function () {
157
+ copy(textOf(block)).then(function () {
158
+ btn.textContent = 'Copied';
159
+ btn.setAttribute('data-copied', '1');
160
+ }, function () {
161
+ btn.textContent = 'Press ⌘C';
162
+ });
163
+ setTimeout(function () {
164
+ btn.textContent = 'Copy';
165
+ btn.removeAttribute('data-copied');
166
+ }, 1600);
167
+ });
168
+ block.appendChild(btn);
169
+ });
170
+ }
171
+
172
+ /* ---------- click-to-copy table cells ---------- */
173
+
174
+ /*
175
+ * Reference tables put one command per cell, where a hovering copy button
176
+ * would crowd the row. The cell itself is the button instead.
177
+ */
178
+ function initCellCopy() {
179
+ var cells = [].slice.call(document.querySelectorAll('[data-copy-cells] td code'));
180
+ cells.forEach(function (cell) {
181
+ var original = cell.textContent;
182
+ cell.tabIndex = 0;
183
+ cell.setAttribute('role', 'button');
184
+ cell.setAttribute('title', 'Click to copy');
185
+ cell.classList.add('copyable');
186
+
187
+ function run() {
188
+ copy(original).then(function () {
189
+ cell.textContent = 'Copied';
190
+ cell.classList.add('copied');
191
+ }, function () {
192
+ cell.textContent = 'Press ⌘C';
193
+ });
194
+ setTimeout(function () {
195
+ cell.textContent = original;
196
+ cell.classList.remove('copied');
197
+ }, 1200);
198
+ }
199
+
200
+ cell.addEventListener('click', run);
201
+ cell.addEventListener('keydown', function (e) {
202
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); run(); }
203
+ });
204
+ });
205
+ }
206
+
207
+ /* ---------- platform filter ---------- */
208
+
209
+ function initFilters() {
210
+ var chips = [].slice.call(document.querySelectorAll('.chip[data-platform]'));
211
+ if (!chips.length) return;
212
+ var rows = [].slice.call(document.querySelectorAll('[data-platforms]'));
213
+
214
+ function select(want) {
215
+ chips.forEach(function (c) {
216
+ c.setAttribute('aria-pressed', String(c.getAttribute('data-platform') === want));
217
+ });
218
+ rows.forEach(function (row) {
219
+ var list = row.getAttribute('data-platforms').split(/\s+/);
220
+ var show = want === 'all' || list.indexOf('all') !== -1 || list.indexOf(want) !== -1;
221
+ row.hidden = !show;
222
+ });
223
+ /* A section whose rows are all hidden says so instead of looking broken. */
224
+ [].slice.call(document.querySelectorAll('[data-filter-section]')).forEach(function (sec) {
225
+ var visible = [].slice.call(sec.querySelectorAll('[data-platforms]')).some(function (r) {
226
+ return !r.hidden;
227
+ });
228
+ sec.classList.toggle('filter-hidden', !visible);
229
+ });
230
+ }
231
+
232
+ chips.forEach(function (c) {
233
+ c.addEventListener('click', function () { select(c.getAttribute('data-platform')); });
234
+ });
235
+ select('all');
236
+ }
237
+
238
+ /* ---------- layer diagram ---------- */
239
+
240
+ function initLayers() {
241
+ var layers = [].slice.call(document.querySelectorAll('.layer[aria-controls]'));
242
+ layers.forEach(function (layer) {
243
+ layer.addEventListener('click', function () {
244
+ var open = layer.getAttribute('aria-expanded') === 'true';
245
+ layers.forEach(function (other) {
246
+ other.setAttribute('aria-expanded', 'false');
247
+ var d = document.getElementById(other.getAttribute('aria-controls'));
248
+ if (d) d.hidden = true;
249
+ });
250
+ if (!open) {
251
+ layer.setAttribute('aria-expanded', 'true');
252
+ var detail = document.getElementById(layer.getAttribute('aria-controls'));
253
+ if (detail) detail.hidden = false;
254
+ }
255
+ });
256
+ });
257
+ }
258
+
259
+ function init() {
260
+ initChecklist();
261
+ initCopy();
262
+ initCellCopy();
263
+ initFilters();
264
+ initLayers();
265
+ }
266
+
267
+ if (document.readyState === 'loading') {
268
+ document.addEventListener('DOMContentLoaded', init);
269
+ } else {
270
+ init();
271
+ }
272
+ })();