@nockchain/rose 0.1.4-nightly.5
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/.github/workflows/artifacts.yml +33 -0
- package/.github/workflows/ci.yml +68 -0
- package/.github/workflows/publish-sdk.yml +35 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/LICENSE +22 -0
- package/README.md +117 -0
- package/extension/background/index.ts +1500 -0
- package/extension/content/index.ts +59 -0
- package/extension/icons/rose.svg +27 -0
- package/extension/icons/rose128.png +0 -0
- package/extension/icons/rose16.png +0 -0
- package/extension/icons/rose256.png +0 -0
- package/extension/icons/rose32.png +0 -0
- package/extension/icons/rose48.png +0 -0
- package/extension/icons/rose512.png +0 -0
- package/extension/inpage/index.ts +86 -0
- package/extension/manifest.json +48 -0
- package/extension/popup/Popup.tsx +94 -0
- package/extension/popup/Router.tsx +121 -0
- package/extension/popup/assets/arrow-down-icon.svg +3 -0
- package/extension/popup/assets/arrow-left-icon.svg +3 -0
- package/extension/popup/assets/arrow-right-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
- package/extension/popup/assets/checkmark-icon.svg +3 -0
- package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
- package/extension/popup/assets/checkmark-success-icon.svg +3 -0
- package/extension/popup/assets/clock-icon.svg +3 -0
- package/extension/popup/assets/close-x-icon.svg +3 -0
- package/extension/popup/assets/copy-icon.svg +6 -0
- package/extension/popup/assets/explorer-icon.svg +3 -0
- package/extension/popup/assets/eye-off-icon.svg +3 -0
- package/extension/popup/assets/eye-open-icon.svg +4 -0
- package/extension/popup/assets/feedback-icon.svg +3 -0
- package/extension/popup/assets/green-status-dot.svg +3 -0
- package/extension/popup/assets/info-icon.svg +3 -0
- package/extension/popup/assets/iris-logo-40.svg +27 -0
- package/extension/popup/assets/iris-logo-96.svg +27 -0
- package/extension/popup/assets/iris-logo-blue.svg +27 -0
- package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
- package/extension/popup/assets/iris-logo-orange.svg +27 -0
- package/extension/popup/assets/iris-logo.svg +27 -0
- package/extension/popup/assets/key-icon.svg +3 -0
- package/extension/popup/assets/lock-icon-yellow.svg +3 -0
- package/extension/popup/assets/lock-icon.svg +3 -0
- package/extension/popup/assets/pencil-edit-icon.svg +3 -0
- package/extension/popup/assets/permissions-icon.svg +3 -0
- package/extension/popup/assets/receipt-icon.svg +5 -0
- package/extension/popup/assets/refresh-icon.svg +3 -0
- package/extension/popup/assets/settings-gear-icon.svg +8 -0
- package/extension/popup/assets/settings-icon.svg +3 -0
- package/extension/popup/assets/theme-icon.svg +3 -0
- package/extension/popup/assets/trash-bin-icon.svg +3 -0
- package/extension/popup/assets/trend-down-arrow.svg +5 -0
- package/extension/popup/assets/trend-up-arrow.svg +5 -0
- package/extension/popup/assets/user-account-icon.svg +3 -0
- package/extension/popup/assets/vector-bottom-left.svg +9 -0
- package/extension/popup/assets/vector-left.svg +9 -0
- package/extension/popup/assets/vector-right.svg +9 -0
- package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
- package/extension/popup/assets/vector-top-right.svg +9 -0
- package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
- package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
- package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
- package/extension/popup/components/AccountIcon.tsx +78 -0
- package/extension/popup/components/AccountSelector.tsx +246 -0
- package/extension/popup/components/Alert.tsx +48 -0
- package/extension/popup/components/ConfirmModal.tsx +81 -0
- package/extension/popup/components/PasswordInput.tsx +49 -0
- package/extension/popup/components/ScreenContainer.tsx +17 -0
- package/extension/popup/components/SiteIcon.tsx +60 -0
- package/extension/popup/components/ThemeToggle.tsx +44 -0
- package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
- package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
- package/extension/popup/components/icons/CheckIcon.tsx +20 -0
- package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
- package/extension/popup/components/icons/CloseIcon.tsx +26 -0
- package/extension/popup/components/icons/CopyIcon.tsx +20 -0
- package/extension/popup/components/icons/EditIcon.tsx +20 -0
- package/extension/popup/components/icons/EyeIcon.tsx +13 -0
- package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
- package/extension/popup/components/icons/InfoIcon.tsx +20 -0
- package/extension/popup/components/icons/LockIcon.tsx +20 -0
- package/extension/popup/components/icons/PlusIcon.tsx +15 -0
- package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
- package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
- package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
- package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
- package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
- package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
- package/extension/popup/components/icons/UploadIcon.tsx +20 -0
- package/extension/popup/components/icons/WalletIcon.tsx +20 -0
- package/extension/popup/contexts/ThemeContext.tsx +105 -0
- package/extension/popup/hooks/useApprovalDetection.ts +128 -0
- package/extension/popup/hooks/useAutoFocus.ts +36 -0
- package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
- package/extension/popup/hooks/useClickOutside.ts +33 -0
- package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
- package/extension/popup/hooks/useFavicon.ts +64 -0
- package/extension/popup/hooks/useNumericInput.ts +93 -0
- package/extension/popup/index.html +13 -0
- package/extension/popup/index.tsx +24 -0
- package/extension/popup/screens/AboutScreen.tsx +118 -0
- package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
- package/extension/popup/screens/HomeScreen.tsx +902 -0
- package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
- package/extension/popup/screens/LockTimeScreen.tsx +155 -0
- package/extension/popup/screens/ReceiveScreen.tsx +149 -0
- package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
- package/extension/popup/screens/SendReviewScreen.tsx +308 -0
- package/extension/popup/screens/SendScreen.tsx +825 -0
- package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
- package/extension/popup/screens/SettingsScreen.tsx +116 -0
- package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
- package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
- package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
- package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
- package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
- package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
- package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
- package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
- package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
- package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
- package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
- package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
- package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
- package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
- package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
- package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
- package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
- package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
- package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
- package/extension/popup/screens/system/LockedScreen.tsx +288 -0
- package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
- package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
- package/extension/popup/store.ts +482 -0
- package/extension/popup/styles.css +246 -0
- package/extension/popup/utils/format.ts +58 -0
- package/extension/popup/utils/formatWalletError.ts +36 -0
- package/extension/popup/utils/memo.ts +299 -0
- package/extension/popup/utils/messaging.ts +16 -0
- package/extension/shared/address-encoding.ts +69 -0
- package/extension/shared/balance-query.ts +123 -0
- package/extension/shared/constants.ts +386 -0
- package/extension/shared/currency.ts +128 -0
- package/extension/shared/first-name-derivation.ts +128 -0
- package/extension/shared/keyfile.ts +58 -0
- package/extension/shared/onboarding.ts +78 -0
- package/extension/shared/price-api.ts +79 -0
- package/extension/shared/rpc-client-browser.ts +315 -0
- package/extension/shared/transaction-builder.ts +443 -0
- package/extension/shared/types.ts +450 -0
- package/extension/shared/utxo-diff.ts +212 -0
- package/extension/shared/utxo-store.ts +548 -0
- package/extension/shared/utxo-sync.ts +343 -0
- package/extension/shared/validators.ts +26 -0
- package/extension/shared/vault.ts +1580 -0
- package/extension/shared/wallet-crypto.ts +77 -0
- package/extension/shared/wasm-utils.ts +76 -0
- package/extension/shared/webcrypto.ts +67 -0
- package/extension/types/wasm.d.ts +13 -0
- package/package.json +39 -0
- package/postcss.config.js +6 -0
- package/rose-extension-dist.zip +0 -0
- package/sdk/README.md +88 -0
- package/sdk/examples/app.ts +166 -0
- package/sdk/examples/index.html +51 -0
- package/sdk/examples/tsconfig.json +15 -0
- package/sdk/examples/tx-builder.html +532 -0
- package/sdk/examples/tx-builder.ts +1766 -0
- package/sdk/package-lock.json +424 -0
- package/sdk/package.json +68 -0
- package/sdk/src/constants.ts +28 -0
- package/sdk/src/errors.ts +74 -0
- package/sdk/src/hooks/index.ts +1 -0
- package/sdk/src/hooks/use-rose.ts +94 -0
- package/sdk/src/index.ts +12 -0
- package/sdk/src/provider.ts +396 -0
- package/sdk/src/transaction.ts +163 -0
- package/sdk/src/types/rose-wasm.d.ts +14 -0
- package/sdk/src/types.ts +97 -0
- package/sdk/src/wasm.ts +13 -0
- package/sdk/tsconfig.json +20 -0
- package/sdk/vite.config.examples.ts +32 -0
- package/tailwind.config.ts +38 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +60 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
/* Monospace-first typography to mirror nocknames.com */
|
|
4
|
+
|
|
5
|
+
@theme {
|
|
6
|
+
/* ============================================================================
|
|
7
|
+
DESIGN TOKENS - Rose Design System
|
|
8
|
+
Source: /Documents/tokens/
|
|
9
|
+
============================================================================ */
|
|
10
|
+
|
|
11
|
+
/* Brand Colors */
|
|
12
|
+
--color-primary: #37f;
|
|
13
|
+
--color-green: #7ccf8b;
|
|
14
|
+
--color-red: #ff6b6b;
|
|
15
|
+
--color-red-light: #2a1a1a;
|
|
16
|
+
|
|
17
|
+
/* Dark Mode Greyscale (Default) */
|
|
18
|
+
--color-bg: #0a0a0b;
|
|
19
|
+
--color-surface-900: #2f2f35;
|
|
20
|
+
--color-surface-800: #151517;
|
|
21
|
+
--color-surface-700: #1c1c1f;
|
|
22
|
+
--color-text-primary: #f5f5f5;
|
|
23
|
+
--color-text-secondary: #c7c7c7;
|
|
24
|
+
--color-text-muted: #9e9ea2;
|
|
25
|
+
--color-divider: #1f1f22;
|
|
26
|
+
|
|
27
|
+
/* Home Screen Specific */
|
|
28
|
+
--color-home-accent: #15151f;
|
|
29
|
+
--color-home-fill: #111317;
|
|
30
|
+
--color-tx-icon: #222222;
|
|
31
|
+
|
|
32
|
+
/* Modals */
|
|
33
|
+
--color-modal-overlay: #11131733;
|
|
34
|
+
--color-popover: #11131766;
|
|
35
|
+
|
|
36
|
+
/* Font Families */
|
|
37
|
+
--font-serif: 'Berkeley Mono', 'BerkeleyMono', 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
38
|
+
--font-sans: 'Berkeley Mono', 'BerkeleyMono', 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
39
|
+
--font-mono: 'Berkeley Mono', 'BerkeleyMono', 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
40
|
+
|
|
41
|
+
/* Font Sizes */
|
|
42
|
+
--font-size-xs: 0.75rem;
|
|
43
|
+
/* 12px */
|
|
44
|
+
--font-size-sm: 0.8125rem;
|
|
45
|
+
/* 13px */
|
|
46
|
+
--font-size-base: 0.875rem;
|
|
47
|
+
/* 14px */
|
|
48
|
+
--font-size-lg: 1rem;
|
|
49
|
+
/* 16px */
|
|
50
|
+
--font-size-xl: 1.5rem;
|
|
51
|
+
/* 24px */
|
|
52
|
+
--font-size-2xl: 2.25rem;
|
|
53
|
+
/* 36px */
|
|
54
|
+
|
|
55
|
+
/* Line Heights */
|
|
56
|
+
--line-height-tight: 1rem;
|
|
57
|
+
/* 16px */
|
|
58
|
+
--line-height-snug: 1.125rem;
|
|
59
|
+
/* 18px */
|
|
60
|
+
--line-height-normal: 1.375rem;
|
|
61
|
+
/* 22px */
|
|
62
|
+
--line-height-relaxed: 1.75rem;
|
|
63
|
+
/* 28px */
|
|
64
|
+
--line-height-loose: 2.5rem;
|
|
65
|
+
/* 40px */
|
|
66
|
+
|
|
67
|
+
/* Letter Spacing */
|
|
68
|
+
--letter-spacing-tight: -0.02em;
|
|
69
|
+
/* -2% */
|
|
70
|
+
--letter-spacing-normal: 0.01em;
|
|
71
|
+
/* 1% */
|
|
72
|
+
--letter-spacing-wide: 0.02em;
|
|
73
|
+
/* 2% */
|
|
74
|
+
|
|
75
|
+
/* Shadows */
|
|
76
|
+
--shadow-xs: 0 0 4px 0 #11131714;
|
|
77
|
+
--shadow-md: 0 4px 12px 0 #1113171f;
|
|
78
|
+
|
|
79
|
+
/* Custom Figma Colors - Light Mode */
|
|
80
|
+
--color-fn-bg: #ebebe9;
|
|
81
|
+
--color-fn-card: #ffffff;
|
|
82
|
+
--color-fn-ink: #000000;
|
|
83
|
+
--color-fn-sub: #707070;
|
|
84
|
+
--color-fn-yellow: #5968fb;
|
|
85
|
+
--color-fn-green: #369929;
|
|
86
|
+
--color-fn-line: #e0e0e0;
|
|
87
|
+
--color-fn-line-muted: #dadad8;
|
|
88
|
+
--color-fn-overlay: rgba(0, 0, 0, 0.6);
|
|
89
|
+
--color-fn-overlay-light: rgba(0, 0, 0, 0.2);
|
|
90
|
+
|
|
91
|
+
/* Custom Border Radius */
|
|
92
|
+
--radius-card: 12px;
|
|
93
|
+
--radius-sheet: 12px;
|
|
94
|
+
--radius-tile: 8px;
|
|
95
|
+
|
|
96
|
+
/* Custom Shadows */
|
|
97
|
+
--shadow-card: 0 6px 18px rgba(0, 0, 0, 0.08);
|
|
98
|
+
|
|
99
|
+
/* Font Display (Berkeley) */
|
|
100
|
+
--font-display: 'Berkeley Mono', 'BerkeleyMono', 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ============================================================================
|
|
104
|
+
LIGHT MODE OVERRIDES
|
|
105
|
+
Applied when .light class is on root element
|
|
106
|
+
============================================================================ */
|
|
107
|
+
|
|
108
|
+
/* Enforce dark theme values even if .light is present */
|
|
109
|
+
html.light {
|
|
110
|
+
--color-bg: #0a0a0b;
|
|
111
|
+
--color-surface-900: #111113;
|
|
112
|
+
--color-surface-800: #151517;
|
|
113
|
+
--color-surface-700: #1c1c1f;
|
|
114
|
+
--color-text-primary: #f5f5f5;
|
|
115
|
+
--color-text-secondary: #c7c7c7;
|
|
116
|
+
--color-text-muted: #9e9ea2;
|
|
117
|
+
--color-divider: #1f1f22;
|
|
118
|
+
--color-green: #7ccf8b;
|
|
119
|
+
--color-red: #ff6b6b;
|
|
120
|
+
--color-red-light: #2a1a1a;
|
|
121
|
+
--color-home-accent: #111113;
|
|
122
|
+
--color-home-fill: #0a0a0b;
|
|
123
|
+
--color-tx-icon: #151517;
|
|
124
|
+
--color-modal-overlay: #11131733;
|
|
125
|
+
--color-popover: #11131766;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Custom base styles */
|
|
129
|
+
body {
|
|
130
|
+
background-color: var(--color-bg);
|
|
131
|
+
color: var(--color-text-primary);
|
|
132
|
+
font-family: var(--font-mono);
|
|
133
|
+
font-size: var(--font-size-base);
|
|
134
|
+
line-height: var(--line-height-normal);
|
|
135
|
+
letter-spacing: var(--letter-spacing-normal);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Ensure all buttons show pointer cursor */
|
|
139
|
+
button {
|
|
140
|
+
cursor: pointer;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Hide scrollbars globally */
|
|
144
|
+
* {
|
|
145
|
+
-ms-overflow-style: none;
|
|
146
|
+
/* IE and Edge */
|
|
147
|
+
scrollbar-width: none;
|
|
148
|
+
/* Firefox */
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
*::-webkit-scrollbar {
|
|
152
|
+
display: none;
|
|
153
|
+
/* Chrome, Safari, Opera */
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Custom component classes */
|
|
157
|
+
.btn-primary {
|
|
158
|
+
width: 100%;
|
|
159
|
+
background: linear-gradient(135deg, #8b55f6, #37f, #0ff);
|
|
160
|
+
color: #000;
|
|
161
|
+
font-family: var(--font-sans);
|
|
162
|
+
font-weight: 500;
|
|
163
|
+
font-size: var(--font-size-base);
|
|
164
|
+
letter-spacing: var(--letter-spacing-normal);
|
|
165
|
+
border-radius: 0.375rem;
|
|
166
|
+
border: 1px solid #3377ff70;
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
transition: opacity 0.2s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.btn-primary:hover {
|
|
172
|
+
opacity: 0.9;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.btn-secondary {
|
|
176
|
+
width: 100%;
|
|
177
|
+
background: linear-gradient(135deg, #23232a, #454553, #222328);
|
|
178
|
+
color: var(--color-text-primary);
|
|
179
|
+
font-family: var(--font-sans);
|
|
180
|
+
font-weight: 500;
|
|
181
|
+
font-size: var(--font-size-base);
|
|
182
|
+
letter-spacing: var(--letter-spacing-normal);
|
|
183
|
+
border-radius: 0.375rem;
|
|
184
|
+
border: 1px solid #3377ff70;
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
transition:
|
|
187
|
+
opacity 0.2s,
|
|
188
|
+
border-color 0.2s;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.btn-secondary:hover {
|
|
192
|
+
opacity: 0.8;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.input-field {
|
|
196
|
+
width: 100%;
|
|
197
|
+
padding: 0.5rem 0.75rem;
|
|
198
|
+
background-color: var(--color-surface-800);
|
|
199
|
+
border: 1px solid var(--color-surface-700);
|
|
200
|
+
border-radius: 0.375rem;
|
|
201
|
+
color: var(--color-text-primary);
|
|
202
|
+
font-family: var(--font-sans);
|
|
203
|
+
font-size: var(--font-size-base);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.input-field::placeholder {
|
|
207
|
+
color: var(--color-text-muted);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.input-field:focus {
|
|
211
|
+
outline: none;
|
|
212
|
+
border-color: var(--color-primary);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.address-display {
|
|
216
|
+
margin: 1rem 0;
|
|
217
|
+
padding: 0.75rem;
|
|
218
|
+
background-color: var(--color-surface-800);
|
|
219
|
+
border-radius: 0.375rem;
|
|
220
|
+
word-break: break-all;
|
|
221
|
+
font-family: monospace;
|
|
222
|
+
font-size: var(--font-size-xs);
|
|
223
|
+
line-height: var(--line-height-snug);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.label {
|
|
227
|
+
font-family: var(--font-sans);
|
|
228
|
+
font-weight: 500;
|
|
229
|
+
font-size: var(--font-size-sm);
|
|
230
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
231
|
+
margin-bottom: 0.25rem;
|
|
232
|
+
color: var(--color-text-secondary);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Hide scrollbar while keeping scroll functionality */
|
|
236
|
+
.no-scrollbar {
|
|
237
|
+
-ms-overflow-style: none;
|
|
238
|
+
/* IE and Edge */
|
|
239
|
+
scrollbar-width: none;
|
|
240
|
+
/* Firefox */
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.no-scrollbar::-webkit-scrollbar {
|
|
244
|
+
display: none;
|
|
245
|
+
/* Chrome, Safari, Opera */
|
|
246
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatting utilities for display
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Truncate an address for display
|
|
7
|
+
* @param address - Full address string
|
|
8
|
+
* @param startChars - Number of characters to show at start (default: 6)
|
|
9
|
+
* @param endChars - Number of characters to show at end (default: 6)
|
|
10
|
+
* @returns Truncated address like "89dF13...sw5Lvw" or empty string if no address
|
|
11
|
+
*/
|
|
12
|
+
export function truncateAddress(
|
|
13
|
+
address: string | null | undefined,
|
|
14
|
+
startChars: number = 6,
|
|
15
|
+
endChars: number = 6
|
|
16
|
+
): string {
|
|
17
|
+
if (!address) return '';
|
|
18
|
+
if (address.length <= startChars + endChars) return address;
|
|
19
|
+
return `${address.slice(0, startChars)}...${address.slice(-endChars)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format a date as a relative time string (e.g., "34m ago", "2d ago")
|
|
24
|
+
* @param date - The date to format
|
|
25
|
+
* @returns Relative time string
|
|
26
|
+
*/
|
|
27
|
+
export function formatTimeAgo(date: Date): string {
|
|
28
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
29
|
+
|
|
30
|
+
if (seconds < 60) return 'Just now';
|
|
31
|
+
const minutes = Math.floor(seconds / 60);
|
|
32
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
33
|
+
const hours = Math.floor(minutes / 60);
|
|
34
|
+
if (hours < 24) return `${hours}h ago`;
|
|
35
|
+
const days = Math.floor(hours / 24);
|
|
36
|
+
return `${days}d ago`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format a timestamp as UTC date/time string
|
|
41
|
+
* @param timestamp - Timestamp in milliseconds since epoch
|
|
42
|
+
* @returns Formatted UTC string (e.g., "Jan 18, 2025, 14:23:45 UTC")
|
|
43
|
+
*/
|
|
44
|
+
export function formatUTCTimestamp(timestamp: number): string {
|
|
45
|
+
const date = new Date(timestamp);
|
|
46
|
+
return (
|
|
47
|
+
date.toLocaleString('en-US', {
|
|
48
|
+
timeZone: 'UTC',
|
|
49
|
+
year: 'numeric',
|
|
50
|
+
month: 'short',
|
|
51
|
+
day: 'numeric',
|
|
52
|
+
hour: '2-digit',
|
|
53
|
+
minute: '2-digit',
|
|
54
|
+
second: '2-digit',
|
|
55
|
+
hour12: false,
|
|
56
|
+
}) + ' UTC'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ERROR_CODES } from '../../shared/constants';
|
|
2
|
+
|
|
3
|
+
type RpcErrorObject = {
|
|
4
|
+
code?: unknown;
|
|
5
|
+
message?: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert mixed backend error shapes into a user-friendly message.
|
|
10
|
+
*
|
|
11
|
+
* The background sometimes returns a string error code (e.g. ERROR_CODES.LOCKED)
|
|
12
|
+
* and sometimes returns an object like { code: -32602, message: 'Invalid params' }.
|
|
13
|
+
*/
|
|
14
|
+
export function formatWalletError(err: unknown): string {
|
|
15
|
+
// Common JS errors
|
|
16
|
+
if (err instanceof Error) return err.message || 'Unknown error';
|
|
17
|
+
|
|
18
|
+
// Background often returns string error codes
|
|
19
|
+
if (typeof err === 'string') {
|
|
20
|
+
if (err === ERROR_CODES.BAD_PASSWORD) return 'Incorrect password';
|
|
21
|
+
if (err === ERROR_CODES.LOCKED) return 'Wallet is locked. Unlock Rose to continue.';
|
|
22
|
+
if (err === ERROR_CODES.UNAUTHORIZED) return 'Unauthorized';
|
|
23
|
+
if (err === ERROR_CODES.INVALID_PARAMS) return 'Invalid parameters';
|
|
24
|
+
return `Error: ${err}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// JSON-RPC style errors: { code, message }
|
|
28
|
+
if (typeof err === 'object' && err !== null) {
|
|
29
|
+
const e = err as RpcErrorObject;
|
|
30
|
+
if (typeof e.message === 'string' && e.message.trim().length > 0) return e.message;
|
|
31
|
+
if (typeof e.code === 'string' || typeof e.code === 'number') return `Error: ${e.code}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback
|
|
35
|
+
return 'Unknown error';
|
|
36
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memo decoding / extraction helpers for transaction approvals.
|
|
3
|
+
*
|
|
4
|
+
* The memo may be represented as:
|
|
5
|
+
* - UTF-8 bytes (Uint8Array)
|
|
6
|
+
* - base64 string
|
|
7
|
+
* - hex string (optionally 0x-prefixed)
|
|
8
|
+
* - nested "protobuf-ish" wrappers like { value: ... }
|
|
9
|
+
*
|
|
10
|
+
* We intentionally keep this resilient: the WASM/protobuf shape may evolve.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { base16, base64, base64url } from '@scure/base';
|
|
14
|
+
import * as wasm from '@nockchain/sdk/wasm';
|
|
15
|
+
import type {
|
|
16
|
+
Note as WasmNote,
|
|
17
|
+
NoteDataEntry as WasmNoteDataEntry,
|
|
18
|
+
RawTx as WasmRawTx,
|
|
19
|
+
} from '@nockchain/sdk/wasm';
|
|
20
|
+
|
|
21
|
+
function isRecord(x: unknown): x is Record<string, unknown> {
|
|
22
|
+
return typeof x === 'object' && x !== null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function unwrapValue(x: unknown): unknown {
|
|
26
|
+
if (!isRecord(x)) return x;
|
|
27
|
+
// Common protobuf-ish wrapper: { value: ... }
|
|
28
|
+
if ('value' in x && Object.keys(x).length === 1) return x.value;
|
|
29
|
+
return x;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hexToBytes(hex: string): Uint8Array | null {
|
|
33
|
+
const normalized = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
34
|
+
if (!/^[0-9a-fA-F]*$/.test(normalized)) return null;
|
|
35
|
+
if (normalized.length % 2 !== 0) return null;
|
|
36
|
+
try {
|
|
37
|
+
// base16 expects hex without 0x prefix and is case-sensitive (RFC 4648 alphabet is uppercase).
|
|
38
|
+
// Normalize to uppercase so mixed/lowercase hex decodes correctly.
|
|
39
|
+
return base16.decode(normalized.toUpperCase());
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function decodeMemoJamListUx(blob: Uint8Array): string | null {
|
|
45
|
+
try {
|
|
46
|
+
const noun = wasm.Noun.cue(blob);
|
|
47
|
+
const jsNoun = noun.toJs(); // atom => hex string, cell => [head, tail]
|
|
48
|
+
|
|
49
|
+
const bytes: number[] = [];
|
|
50
|
+
let x: unknown = jsNoun;
|
|
51
|
+
|
|
52
|
+
while (Array.isArray(x) && x.length === 2) {
|
|
53
|
+
const head = x[0];
|
|
54
|
+
const tail = x[1];
|
|
55
|
+
|
|
56
|
+
if (typeof head !== 'string') return null;
|
|
57
|
+
const v = Number.parseInt(head, 16);
|
|
58
|
+
if (!Number.isFinite(v) || v < 0 || v > 255) return null;
|
|
59
|
+
|
|
60
|
+
bytes.push(v);
|
|
61
|
+
x = tail;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// list terminator must be atom 0 (hex string "0")
|
|
65
|
+
if (typeof x !== 'string') return null;
|
|
66
|
+
if (Number.parseInt(x, 16) !== 0) return null;
|
|
67
|
+
|
|
68
|
+
const decoded = bytesToUtf8(Uint8Array.from(bytes)).trim();
|
|
69
|
+
return decoded.length ? decoded : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function decodeMemoViaWasm(blob: Uint8Array): string | null {
|
|
76
|
+
try {
|
|
77
|
+
// Prefer the dedicated helper if present on the rose-wasm module (newer builds).
|
|
78
|
+
// We access it via `any` so the extension can compile against older rose-wasm typings.
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
|
+
const fn = (wasm as any).decodeMemoUtf8 as undefined | ((b: Uint8Array) => string);
|
|
81
|
+
|
|
82
|
+
if (!fn) return null;
|
|
83
|
+
const s = fn(blob);
|
|
84
|
+
if (typeof s !== 'string') return null;
|
|
85
|
+
const trimmed = s.trim();
|
|
86
|
+
return trimmed.length ? trimmed : null;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function base64ToBytes(b64: string): Uint8Array | null {
|
|
92
|
+
const normalized = b64.trim();
|
|
93
|
+
if (!normalized) return null;
|
|
94
|
+
|
|
95
|
+
// Heuristics to avoid interpreting arbitrary plaintext as base64/base64url.
|
|
96
|
+
// - base64: A–Z a–z 0–9 + / with optional = padding, length must be multiple of 4.
|
|
97
|
+
// - base64url: A–Z a–z 0–9 - _ with optional padding; may be unpadded.
|
|
98
|
+
const looksLikeB64 = /^[A-Za-z0-9+/]+={0,2}$/.test(normalized) && normalized.length % 4 === 0;
|
|
99
|
+
const looksLikeB64Url = /^[A-Za-z0-9_-]+={0,2}$/.test(normalized);
|
|
100
|
+
if (!looksLikeB64 && !looksLikeB64Url) return null;
|
|
101
|
+
|
|
102
|
+
// Try strict decoders first; fall back to normalizing url-safe -> standard base64.
|
|
103
|
+
try {
|
|
104
|
+
if (looksLikeB64) return base64.decode(normalized);
|
|
105
|
+
if (looksLikeB64Url) return base64url.decode(normalized);
|
|
106
|
+
} catch {
|
|
107
|
+
// continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// Normalize base64url (unpadded) to standard base64 for maximum compatibility.
|
|
112
|
+
let s = normalized.replace(/-/g, '+').replace(/_/g, '/');
|
|
113
|
+
const pad = s.length % 4;
|
|
114
|
+
if (pad === 2) s += '==';
|
|
115
|
+
else if (pad === 3) s += '=';
|
|
116
|
+
else if (pad === 1) return null; // impossible length
|
|
117
|
+
return base64.decode(s);
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function bytesToUtf8(bytes: Uint8Array): string {
|
|
124
|
+
try {
|
|
125
|
+
return new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
|
126
|
+
} catch {
|
|
127
|
+
// Fallback (very old environments)
|
|
128
|
+
let s = '';
|
|
129
|
+
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
130
|
+
return s;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function looksLikeReadableText(s: string): boolean {
|
|
135
|
+
if (s.includes('\uFFFD')) return false;
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < s.length; i++) {
|
|
138
|
+
const c = s.charCodeAt(i);
|
|
139
|
+
const isAllowedWhitespace = c === 0x09 || c === 0x0a || c === 0x0d;
|
|
140
|
+
const isControl = (c >= 0x00 && c <= 0x1f) || (c >= 0x7f && c <= 0x9f);
|
|
141
|
+
if (isControl && !isAllowedWhitespace) return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function decodeMemoValue(maybe: unknown): string | null {
|
|
148
|
+
const v = unwrapValue(maybe);
|
|
149
|
+
if (v == null) return null;
|
|
150
|
+
|
|
151
|
+
if (v instanceof Uint8Array) {
|
|
152
|
+
// Prefer decoding via rose-wasm helper (keeps logic consistent with chain encoding).
|
|
153
|
+
const wasmDecoded = decodeMemoViaWasm(v);
|
|
154
|
+
if (wasmDecoded) return wasmDecoded;
|
|
155
|
+
|
|
156
|
+
// Fallback: decode jammed `(list @ux)` noun directly.
|
|
157
|
+
const jamDecoded = decodeMemoJamListUx(v);
|
|
158
|
+
if (jamDecoded) return jamDecoded;
|
|
159
|
+
|
|
160
|
+
// fallback (legacy / unexpected)
|
|
161
|
+
const decoded = bytesToUtf8(v).trim();
|
|
162
|
+
return decoded.length ? decoded : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// NEW: protobuf blobs may come through as number[]
|
|
166
|
+
if (Array.isArray(v) && v.every(n => Number.isInteger(n) && n >= 0 && n <= 255)) {
|
|
167
|
+
return decodeMemoValue(Uint8Array.from(v));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (typeof v === 'string') {
|
|
171
|
+
const raw = v.trim();
|
|
172
|
+
if (!raw) return null;
|
|
173
|
+
|
|
174
|
+
const asHex = hexToBytes(raw);
|
|
175
|
+
if (asHex) {
|
|
176
|
+
const jamDecoded = decodeMemoJamListUx(asHex);
|
|
177
|
+
if (jamDecoded) return jamDecoded;
|
|
178
|
+
|
|
179
|
+
const decoded = bytesToUtf8(asHex).trim();
|
|
180
|
+
if (decoded.length && looksLikeReadableText(decoded)) return decoded;
|
|
181
|
+
return raw;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const asB64 = base64ToBytes(raw);
|
|
185
|
+
if (asB64) {
|
|
186
|
+
const jamDecoded = decodeMemoJamListUx(asB64);
|
|
187
|
+
if (jamDecoded) return jamDecoded;
|
|
188
|
+
|
|
189
|
+
const decoded = bytesToUtf8(asB64).trim();
|
|
190
|
+
if (decoded.length && looksLikeReadableText(decoded)) return decoded;
|
|
191
|
+
return raw;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return raw;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (isRecord(v)) {
|
|
198
|
+
if ('blob' in v) return decodeMemoValue(v.blob);
|
|
199
|
+
if ('memo' in v) return decodeMemoValue(v.memo);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
type ProtobufNoteDataEntryLike = { key?: unknown; blob?: unknown };
|
|
206
|
+
|
|
207
|
+
function isWasmNote(note: unknown): note is WasmNote {
|
|
208
|
+
return isRecord(note) && 'noteData' in note;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getNoteDataEntries(
|
|
212
|
+
note: WasmNote | unknown
|
|
213
|
+
): ReadonlyArray<WasmNoteDataEntry | ProtobufNoteDataEntryLike> {
|
|
214
|
+
// WASM path (strongly typed)
|
|
215
|
+
if (isWasmNote(note)) {
|
|
216
|
+
const noteData: any = (note as any).noteData;
|
|
217
|
+
const entries =
|
|
218
|
+
typeof noteData?.entries === 'function' ? noteData.entries() : noteData?.entries;
|
|
219
|
+
return Array.isArray(entries) ? entries : [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Protobuf-ish object path (resilient)
|
|
223
|
+
if (!isRecord(note)) return [];
|
|
224
|
+
const v1 = (note as any)?.note_version?.V1 ?? (note as any)?.noteVersion?.V1 ?? null;
|
|
225
|
+
const noteData = v1?.note_data ?? v1?.noteData ?? null;
|
|
226
|
+
const entries = noteData?.entries ?? noteData?.Entries ?? null;
|
|
227
|
+
return Array.isArray(entries) ? (entries as ProtobufNoteDataEntryLike[]) : [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function extractMemoFromOutputs(
|
|
231
|
+
outputs: ReadonlyArray<WasmNote | unknown> | undefined
|
|
232
|
+
): string | null {
|
|
233
|
+
if (!outputs || !Array.isArray(outputs)) return null;
|
|
234
|
+
|
|
235
|
+
for (const output of outputs) {
|
|
236
|
+
const entries = getNoteDataEntries(output);
|
|
237
|
+
for (const entry of entries) {
|
|
238
|
+
const keyRaw = (entry as any)?.key;
|
|
239
|
+
const keyVal = typeof keyRaw === 'function' ? keyRaw.call(entry) : keyRaw;
|
|
240
|
+
const key = unwrapValue(keyVal);
|
|
241
|
+
const keyStr = typeof key === 'string' ? key : null;
|
|
242
|
+
if (!keyStr) continue;
|
|
243
|
+
if (keyStr.toLowerCase() !== 'memo') continue;
|
|
244
|
+
|
|
245
|
+
const blobRaw = (entry as any)?.blob;
|
|
246
|
+
const blobVal = typeof blobRaw === 'function' ? blobRaw.call(entry) : blobRaw;
|
|
247
|
+
const memo = decodeMemoValue(blobVal);
|
|
248
|
+
if (memo) return memo;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function extractMemoFromRawTx(rawTx: WasmRawTx | unknown): string | null {
|
|
256
|
+
// Fast-path: common shapes
|
|
257
|
+
const maybe = isRecord(rawTx) ? rawTx : (rawTx as unknown as Record<string, unknown>);
|
|
258
|
+
const direct = decodeMemoValue((maybe as any)?.memo ?? (maybe as any)?.Memo);
|
|
259
|
+
if (direct) return direct;
|
|
260
|
+
|
|
261
|
+
// Some schemas may tuck memo under tx/body/etc.
|
|
262
|
+
const nested = decodeMemoValue(
|
|
263
|
+
(maybe as any)?.tx?.memo ?? (maybe as any)?.body?.memo ?? (maybe as any)?.transaction?.memo
|
|
264
|
+
);
|
|
265
|
+
if (nested) return nested;
|
|
266
|
+
|
|
267
|
+
// As a last resort, shallow scan for keys named "memo" (case-insensitive) up to a small depth.
|
|
268
|
+
const queue: Array<{ obj: Record<string, unknown>; depth: number }> = [];
|
|
269
|
+
if (isRecord(rawTx)) queue.push({ obj: rawTx, depth: 0 });
|
|
270
|
+
const seen = new Set<object>();
|
|
271
|
+
|
|
272
|
+
while (queue.length) {
|
|
273
|
+
const { obj, depth } = queue.shift()!;
|
|
274
|
+
if (seen.has(obj)) continue;
|
|
275
|
+
seen.add(obj);
|
|
276
|
+
|
|
277
|
+
for (const [k, val] of Object.entries(obj)) {
|
|
278
|
+
if (k.toLowerCase() === 'memo') {
|
|
279
|
+
const found = decodeMemoValue(val);
|
|
280
|
+
if (found) return found;
|
|
281
|
+
}
|
|
282
|
+
if (depth < 3 && isRecord(val)) queue.push({ obj: val, depth: depth + 1 });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Extract a human-readable memo from a signRawTx payload.
|
|
291
|
+
* Returns null if no memo exists.
|
|
292
|
+
*/
|
|
293
|
+
export function extractMemo(params: {
|
|
294
|
+
rawTx: WasmRawTx | unknown;
|
|
295
|
+
outputs?: ReadonlyArray<WasmNote | unknown>;
|
|
296
|
+
}): string | null {
|
|
297
|
+
// Prefer explicit tx-level memo if present; otherwise fall back to note_data entries.
|
|
298
|
+
return extractMemoFromRawTx(params.rawTx) ?? extractMemoFromOutputs(params.outputs);
|
|
299
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging utilities for communicating with the service worker
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Note - Extension has separate isolated contexts which cannot directly call each other's functions.
|
|
6
|
+
// Must use Chrome's message passing API to communicate between popup, content scripts, and background service worker.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send a message to the service worker
|
|
10
|
+
* @param method - The method to call (from INTERNAL_METHODS or PROVIDER_METHODS)
|
|
11
|
+
* @param params - Optional parameters to pass
|
|
12
|
+
* @returns Promise with the response from the service worker
|
|
13
|
+
*/
|
|
14
|
+
export function send<T = unknown>(method: string, params?: unknown[]): Promise<T> {
|
|
15
|
+
return chrome.runtime.sendMessage({ payload: { method, params } });
|
|
16
|
+
}
|