Skip to content

Commit a0dc5fc

Browse files
committed
feat: integrate lmp with v1
1 parent df616ac commit a0dc5fc

File tree

10 files changed

+233
-2
lines changed

10 files changed

+233
-2
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ Linking **UI Toolkit** on package level, allows you to develop components in iso
133133
[shared-wallets]: ./packages/shared-wallets
134134
[staking]: ./packages/staking
135135

136+
## Creating an LMP Bundle Build
137+
To create an LMP bundle for Lace:
138+
- Place the LMP build at the repository root in `./lmp-build`
139+
- From the repository root run `yarn build:bundle:full`
140+
- The command will produce a `.dist` file in the repository root.
141+
136142
## Audit
137143

138144
Lace has been independently audited and manually verified by external auditor, [FYEO](https://www.fyeo.io/), so the Lace team can improve code quality and security – giving you greater peace of mind. You can view the full report at [lace.io/lace-audit-report](https://lace.io/lace-audit-report)

apps/browser-extension-wallet/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"watch": " run-s cleanup:dist watch:project",
5656
"watch:app": "run -T webpack --config webpack.app.dev.js --progress --watch",
5757
"watch:sw": "run -T webpack --config webpack.sw.dev.js --progress --watch",
58-
"watch:project": "run-p watch:sw watch:app"
58+
"watch:project": "run-p watch:sw watch:app",
59+
"build:bundle:prepare": "yarn cleanup:dist && WEBPACK_PUBLIC_PATH=/sw/ yarn build:sw && WEBPACK_PUBLIC_PATH=/app/ yarn build:app && WEBPACK_PUBLIC_PATH=/app/ yarn build:cs"
5960
},
6061
"dependencies": {
6162
"@ant-design/icons": "^4.7.0",

assets/popup.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<link rel="preconnect" href="https://use.typekit.net" crossorigin />
8+
<link rel="stylesheet" href="https://use.typekit.net/ksk5ywd.css" />
9+
10+
<title>Lace</title>
11+
<link rel="stylesheet" href="assets/preloader.css" />
12+
<style>
13+
html,
14+
head,
15+
body {
16+
height: 600px;
17+
width: 360px;
18+
margin: 0;
19+
padding: 0;
20+
}
21+
</style>
22+
<script src="popup-bundle.js"></script>
23+
</head>
24+
<body id="lace-popup-body">
25+
<div id="preloader" class="overlay loaderContainer" data-testid="preloader">
26+
<div class="imageContainer">
27+
<img
28+
alt="loader"
29+
src="assets/loader.png"
30+
class="loaderImage"
31+
data-testid="preloader-image"
32+
/>
33+
</div>
34+
<p class="loaderText" data-testid="preloader-text">Loading...</p>
35+
</div>
36+
<div id="lace-popup"></div>
37+
</body>
38+
</html>

bundle-webpack.config.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const path = require('path');
2+
const CopyPlugin = require('copy-webpack-plugin');
3+
const MergeManifestPlugin = require('./merge-manifest-plugin');
4+
5+
module.exports = {
6+
mode: 'development',
7+
devtool: 'source-map',
8+
entry: {
9+
'sw-bundle': path.join(__dirname, 'lmp-bundler', 'sw-bundle.js'),
10+
'popup-bundle': path.join(__dirname, 'lmp-bundler', 'popup-bundle.js')
11+
},
12+
output: {
13+
filename: '[name].js',
14+
path: path.join(__dirname, 'dist')
15+
},
16+
resolve: {
17+
extensions: ['.js', '.json']
18+
},
19+
plugins: [
20+
new MergeManifestPlugin(),
21+
new CopyPlugin({
22+
patterns: [
23+
// Copy assets
24+
{
25+
from: path.join(__dirname, 'assets'),
26+
to: path.join(__dirname, 'dist')
27+
},
28+
// Copy entire LMP dist
29+
// Luckily, there are no conflicting filenames so we can copy both to root dist/
30+
{
31+
from: path.join(__dirname, 'lmp-build', 'dist'),
32+
to: path.join(__dirname, 'dist'),
33+
globOptions: {
34+
ignore: ['**/manifest.json', '**/popup.html']
35+
}
36+
},
37+
// Copy entire V1 dist
38+
{
39+
from: path.join(__dirname, 'apps', 'browser-extension-wallet', 'dist'),
40+
to: path.join(__dirname, 'dist'),
41+
globOptions: {
42+
ignore: ['**/manifest.json', '**/popup.html', '**/load-popup.js']
43+
}
44+
}
45+
]
46+
})
47+
]
48+
};

lmp-build/.gitkeep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lmp-build/

lmp-bundler/const.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Shared constants
2+
export const STORAGE_KEY = {
3+
APP_MODE: "lace-app-mode",
4+
};
5+
export const APP_MODE = {
6+
LMP: "LMP",
7+
V1: "V1",
8+
};

lmp-bundler/popup-bundle.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Popup bundle - switches between V1 and LMP based on stored mode
2+
import { storage } from 'webextension-polyfill';
3+
import { STORAGE_KEY, APP_MODE } from './const';
4+
5+
const stored = await storage.local.get(STORAGE_KEY.APP_MODE);
6+
const mode = stored[STORAGE_KEY.APP_MODE];
7+
8+
if (mode === APP_MODE.LMP) {
9+
console.log('popup-bundle: loading LMP popup script');
10+
// Load Lace Midnight Preview popup script
11+
loadScript('/js/popup-script.js');
12+
} else {
13+
// Default to Lace V1 (Cardano+Bitcoin)
14+
console.log('popup-bundle: loading V1 popup script');
15+
loadScript('/app/popup.js');
16+
}
17+
18+
// PERF: appending to body might perform better
19+
function loadScript(src) {
20+
console.log(`popup-bundle: loading script ${src}`);
21+
const script = document.createElement('script');
22+
script.src = src;
23+
document.head.appendChild(script);
24+
}

lmp-bundler/sw-bundle.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Service worker bundle
2+
// SW scripts have to be imported synchronously (or very fast, if async)
3+
4+
// Flag to identify if this is a LMP bundle
5+
globalThis.LMP_BUNDLE = true
6+
7+
// v1
8+
importScripts("/sw/background.js");
9+
// LMP
10+
importScripts("/js/sw/sw-script.js");

merge-manifest-plugin.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
class MergeManifestPlugin {
5+
constructor(options = {}) {
6+
this.options = options;
7+
}
8+
9+
apply(compiler) {
10+
compiler.hooks.initialize.tap('MergeManifestPlugin', () => {
11+
console.log('[MergeManifestPlugin] Manifest merge initialized\n');
12+
});
13+
14+
compiler.hooks.thisCompilation.tap('MergeManifestPlugin', (compilation) => {
15+
compilation.hooks.processAssets.tap(
16+
{
17+
name: 'MergeManifestPlugin',
18+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
19+
},
20+
() => {
21+
const distDir = path.resolve(__dirname, 'dist');
22+
if (!fs.existsSync(distDir)) {
23+
fs.mkdirSync(distDir);
24+
}
25+
26+
// Read v1 manifest as base
27+
const v1ManifestPath = path.join(__dirname, 'apps', 'browser-extension-wallet', 'dist', 'manifest.json');
28+
const v1Manifest = JSON.parse(fs.readFileSync(v1ManifestPath, 'utf-8'));
29+
30+
// Read LMP manifest for CSP merging
31+
const lmpManifestPath = path.join(__dirname, 'lmp-build', 'dist', 'manifest.json');
32+
const lmpManifest = JSON.parse(fs.readFileSync(lmpManifestPath, 'utf-8'));
33+
34+
v1Manifest.name = 'Lace';
35+
36+
// Update service worker to use our sw-bundle.js
37+
v1Manifest.background.service_worker = './sw-bundle.js';
38+
39+
// Update popup to use the bundle entrypoint, which is same as in v1 but doesn't load the script
40+
v1Manifest.action.default_popup = './popup.html';
41+
42+
for (const script of lmpManifest.content_scripts) {
43+
v1Manifest.content_scripts.push(script);
44+
}
45+
46+
// Merge CSP connect-src values
47+
const v1CSP = v1Manifest.content_security_policy.extension_pages;
48+
const lmpCSP = lmpManifest.content_security_policy.extension_pages;
49+
50+
// Extract connect-src from both CSPs
51+
const v1ConnectMatch = v1CSP.match(/connect-src ([^;]+)/);
52+
const lmpConnectMatch = lmpCSP.match(/connect-src ([^;]+)/);
53+
54+
if (v1ConnectMatch && lmpConnectMatch) {
55+
// Parse connect-src URLs
56+
const v1ConnectUrls = new Set(v1ConnectMatch[1].trim().split(/\s+/));
57+
const lmpConnectUrls = new Set(lmpConnectMatch[1].trim().split(/\s+/));
58+
59+
// Merge URLs (union of both sets)
60+
const mergedConnectUrls = new Set([...v1ConnectUrls, ...lmpConnectUrls]);
61+
62+
// Reconstruct CSP with merged connect-src
63+
const mergedConnectSrc = Array.from(mergedConnectUrls).join(' ');
64+
v1Manifest.content_security_policy.extension_pages = v1CSP.replace(
65+
/connect-src [^;]+/,
66+
`connect-src ${mergedConnectSrc}`
67+
);
68+
}
69+
70+
// Merge web_accessible_resources
71+
if (lmpManifest.web_accessible_resources) {
72+
for (const lmpResource of lmpManifest.web_accessible_resources) {
73+
v1Manifest.web_accessible_resources.push(lmpResource);
74+
}
75+
}
76+
77+
// Write merged manifest
78+
fs.writeFileSync(path.join(distDir, 'manifest.json'), JSON.stringify(v1Manifest, null, 2), 'utf-8');
79+
80+
console.log('[MergeManifestPlugin] Manifest merged successfully');
81+
}
82+
);
83+
});
84+
85+
compiler.hooks.done.tap('MergeManifestPlugin', () => {
86+
console.log('\n[MergeManifestPlugin] Manifest merge finalized!\n');
87+
});
88+
}
89+
}
90+
91+
module.exports = MergeManifestPlugin;

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@
4444
"type-check": "yarn workspaces foreach -ptv run type-check",
4545
"version": "standard-version",
4646
"watch": "yarn base-command-for-build-scripts --done-criteria 'created|(webpack [\\d\\.]+ compiled)|(.*STAKING.*CJS.*Build success)' -c watch",
47-
"watch-deps": "EXCLUDE_APP=true yarn watch"
47+
"watch-deps": "EXCLUDE_APP=true yarn watch",
48+
"build:bundle:v1": "yarn workspace @lace/browser-extension-wallet build:bundle:prepare",
49+
"build:webpack": "webpack -c bundle-webpack.config.js",
50+
"build:bundle:merge": "rm -rf ./dist && yarn run build:webpack",
51+
"build:bundle:full": "run-s build:bundle:v1 build:bundle:merge"
4852
},
4953
"lint-staged": {
5054
"*(apps/**/*.{js,ts,tsx}|packages/!(translation|nami)/**/*.{js,ts,tsx}|stories/**/*.{js,ts,tsx})": [

0 commit comments

Comments
 (0)