Skip to content

Commit 4b5ca00

Browse files
committed
feat: bundle lmp with v1
1 parent 4b28ad8 commit 4b5ca00

27 files changed

+3074
-13
lines changed

.cache/eslintcache

Lines changed: 0 additions & 1 deletion
This file was deleted.

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,40 @@
11
.idea
22
.pnp.cjs
33
.yarn
4+
node_modules
5+
dist
6+
.env
7+
.history
8+
.lh
9+
*.crx
10+
*.d.ts
11+
*.ts.map
12+
*.tsbuildinfo
13+
!declaration.d.ts
14+
*dist/
15+
*demo/
16+
*node_modules/
17+
*yarn-error.log
18+
storybook-static
19+
build-storybook.log
20+
.DS_Store
21+
coverage
22+
.rollup.cache
23+
.tool-versions
24+
.vscode
25+
yalc.lock
26+
27+
# PRJ Spec
28+
/.cache
29+
30+
# Nix
31+
result
32+
33+
.yarn/*
34+
.pnp.*
35+
.yalc/*
36+
!.yarn/patches
37+
!.yarn/plugins
38+
!.yarn/releases
39+
!.yarn/sdks
40+
!.yarn/versions

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "v2"]
2+
path = v2
3+
url = git@github.com:input-output-hk/lace-platform.git

.mcp.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"mcpServers": {
3+
"sequential-thinking": {
4+
"type": "stdio",
5+
"command": "npx",
6+
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"],
7+
"env": {}
8+
},
9+
"context7": {
10+
"type": "stdio",
11+
"command": "npx",
12+
"args": ["-y", "@upstash/context7-mcp"],
13+
"env": {}
14+
},
15+
"interactive": {
16+
"command": "npx",
17+
"args": [
18+
"-y",
19+
"interactive-mcp",
20+
"-t",
21+
"3600",
22+
"--disable-tools",
23+
"message_complete_notification"
24+
]
25+
}
26+
}
27+
}

.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

CONTRIBUTING.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Getting Started
2+
3+
```bash
4+
# Clone with submodules
5+
git clone --recurse-submodules https://github.com/input-output-hk/lace
6+
7+
cd v1
8+
# follow repo setup steps, run a full `yarn build` to prepare internal monorepo package dependencies
9+
10+
cd v2
11+
# follow repo setup steps, run a full `npm run build` to prepare internal monorepo package dependencies
12+
```
13+
14+
## Bundle Build
15+
16+
```bash
17+
# Development build of both applications + bundle
18+
yarn build:dev
19+
```
20+
21+
See [package.json](./package.json) scripts for more granularity.

Makefile

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.PHONY: build-dev-v1-app build-dev-lmp-app build-prod-v1-app build-prod-lmp-app
2+
3+
# Reusable functions
4+
define get-extension-id
5+
$(shell cat v1/apps/browser-extension-wallet/.env.defaults | grep LACE_EXTENSION_ID | cut -d'=' -f2)
6+
endef
7+
8+
define build-v1-app
9+
@echo "🔨 Building Lace v1 ($(1))..."
10+
cd ./v1/apps/browser-extension-wallet && \
11+
yarn cleanup:dist && \
12+
$(2)WEBPACK_PUBLIC_PATH=/sw/ yarn build:sw && \
13+
$(2)WEBPACK_PUBLIC_PATH=/app/ yarn build:app && \
14+
$(2)WEBPACK_PUBLIC_PATH=/app/ yarn build:cs
15+
@echo "✅ Built to ./v1/apps/browser-extension-wallet/dist"
16+
endef
17+
18+
define build-lmp-app
19+
@echo "🔨 Building Lace Midnight Preview ($(1))..."
20+
$(eval EXTENSION_ID := $(call get-extension-id))
21+
cd ./v2/apps/midnight-extension && \
22+
rm -rf ./dist && \
23+
EXTENSION_ID=$(EXTENSION_ID) NODE_ENV=$(1) npm run $(2) && \
24+
EXTENSION_ID=$(EXTENSION_ID) NODE_ENV=$(1) EXTRA_FEATURE_FLAGS=LMP_BUNDLE WEBPACK_PUBLIC_PATH=/js/sw/ npm run $(3)
25+
@echo "✅ Built to ./v2/apps/midnight-extension/dist"
26+
endef
27+
28+
# Development builds
29+
build-dev-v1-app:
30+
$(call build-v1-app,development,WEBPACK_ENV=dev )
31+
32+
build-dev-lmp-app:
33+
$(call build-lmp-app,development,build:dev:app,build:dev:sw)
34+
35+
# Production builds
36+
build-prod-v1-app:
37+
$(call build-v1-app,production,)
38+
39+
build-prod-lmp-app:
40+
$(call build-lmp-app,production,build:app,build:sw)

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Working with this repository
2+
3+
See [CONTRIBUTING.md](./CONTRIBUTING.md)
4+
5+
# Context
6+
7+
We have lace and the lace-platform connected through a submodule as a git submodule
8+
9+
## Lace Cardano+Bitcoin (aka. V1): `./v1/apps/browser-extension-wallet`
10+
11+
### Entrypoints
12+
13+
- Extension manifest: `./v1/apps/browser-extension-wallet/dist/manifest.json` and associated non-UI scripts:
14+
- Service worker: `./v1/apps/browser-extension-wallet/dist/sw/background.js`
15+
- Isolated content script: `./v1/apps/browser-extension-wallet/dist/app/content.js`
16+
- Injected content script: `./v1/apps/browser-extension-wallet/dist/app/inject.js`
17+
- Trezor content script: `./v1/apps/browser-extension-wallet/dist/app/trezor-content-script.js`
18+
- UI pages (imports js scripts and some assets):
19+
- Tab UI: `./v1/apps/browser-extension-wallet/dist/app.html`
20+
- Web extension popup UI: `./v1/apps/browser-extension-wallet/dist/popup.html`
21+
- dApp Connector popup: `./v1/apps/browser-extension-wallet/dist/dappConnector.html`
22+
- Trezor: `./v1/apps/browser-extension-wallet/dist/trezor-usb-permissions.html`
23+
24+
All scripts are bundled and minified. Most of them dynamically loads other .js scripts and assets such as wasm and images.
25+
26+
## Lace Midnight Preview (aka. LMP): `./lace-platform/apps/midnight-extension`
27+
28+
### Entrypoints
29+
30+
- Extension manifest: `./lace-platform/apps/midnight-extension/dist/manifest.json` and associated non-UI scripts:
31+
- Service worker: `./lace-platform/apps/midnight-extension/dist/js/sw/sw-script.js`
32+
- Isolated content script: `./lace-platform/apps/midnight-extension/dist/js/isolated-script.js`
33+
- Injected content script: `./lace-platform/apps/midnight-extension/dist/js/injected-script.js`
34+
- UI pages (imports js scripts and some assets):
35+
- Tab UI: `./lace-platform/apps/midnight-extension/dist/tab.html`
36+
- Web extension popup UI: `./lace-platform/apps/midnight-extension/dist/popup.html`
37+
38+
All scripts are bundled and minified. Most of them dynamically loads other .js scripts and assets such as wasm and images.
39+
40+
# Project
41+
42+
New web extension application that bundles both existing applications into 1. Published as an update to Lace v1.
43+
44+
The goal is to bundle applications with minimal modifications in each application - ideally, only adding UI elements to switch between the 2 applications.
45+
46+
## Technical Approach
47+
48+
### Build
49+
50+
Webpack build that:
51+
52+
- copies `dist/` artifacts from both applications (there are currently no conflicting filenames)
53+
- js, html, wasm files, images, fonts, css etc.
54+
- merges manifest.json of both applications. uses V1 manifest as a base and patches it with:
55+
- new entrypoints:
56+
- [service worker](./src/sw-bundle.js) - loads SW script of both apps
57+
- [popup](./src/popup-bundle.js) - popup html is the same as in v1, but with a different script that conditionally loads the script of the active application
58+
- additional `content_scripts` from LMP
59+
- additional `content_security_policy` rules from LMP
60+
- additional `web_accessible_resources` rules from LMP
61+
62+
### Mode Switching Mechanism
63+
64+
- Service worker always loads both application scripts
65+
- Content scripts from both apps (injected & isolated) are always loaded
66+
- Extension popup switches the mode based on active wallet (determined by value stored in extension storage)
67+
- All other UIs (popup, dapp connector) are addressed as different html files - no changes

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>

merge-manifest-plugin.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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(
28+
__dirname,
29+
"v1",
30+
"apps",
31+
"browser-extension-wallet",
32+
"dist",
33+
"manifest.json"
34+
);
35+
const v1Manifest = JSON.parse(
36+
fs.readFileSync(v1ManifestPath, "utf-8")
37+
);
38+
39+
// Read LMP manifest for CSP merging
40+
const lmpManifestPath = path.join(
41+
__dirname,
42+
"v2",
43+
"apps",
44+
"midnight-extension",
45+
"dist",
46+
"manifest.json"
47+
);
48+
const lmpManifest = JSON.parse(
49+
fs.readFileSync(lmpManifestPath, "utf-8")
50+
);
51+
52+
v1Manifest.name = "Lace";
53+
54+
// Update service worker to use our sw-bundle.js
55+
v1Manifest.background.service_worker = "./sw-bundle.js";
56+
57+
// Update popup to use the bundle entrypoint, which is same as in v1 but doesn't load the script
58+
v1Manifest.action.default_popup = "./popup.html";
59+
60+
for (const script of lmpManifest.content_scripts) {
61+
v1Manifest.content_scripts.push(script);
62+
}
63+
64+
// Merge CSP connect-src values
65+
const v1CSP = v1Manifest.content_security_policy.extension_pages;
66+
const lmpCSP = lmpManifest.content_security_policy.extension_pages;
67+
68+
// Extract connect-src from both CSPs
69+
const v1ConnectMatch = v1CSP.match(/connect-src ([^;]+)/);
70+
const lmpConnectMatch = lmpCSP.match(/connect-src ([^;]+)/);
71+
72+
if (v1ConnectMatch && lmpConnectMatch) {
73+
// Parse connect-src URLs
74+
const v1ConnectUrls = new Set(
75+
v1ConnectMatch[1].trim().split(/\s+/)
76+
);
77+
const lmpConnectUrls = new Set(
78+
lmpConnectMatch[1].trim().split(/\s+/)
79+
);
80+
81+
// Merge URLs (union of both sets)
82+
const mergedConnectUrls = new Set([
83+
...v1ConnectUrls,
84+
...lmpConnectUrls,
85+
]);
86+
87+
// Reconstruct CSP with merged connect-src
88+
const mergedConnectSrc = Array.from(mergedConnectUrls).join(" ");
89+
v1Manifest.content_security_policy.extension_pages = v1CSP.replace(
90+
/connect-src [^;]+/,
91+
`connect-src ${mergedConnectSrc}`
92+
);
93+
}
94+
95+
// Merge web_accessible_resources
96+
if (lmpManifest.web_accessible_resources) {
97+
for (const lmpResource of lmpManifest.web_accessible_resources) {
98+
v1Manifest.web_accessible_resources.push(lmpResource);
99+
}
100+
}
101+
102+
// Write merged manifest
103+
fs.writeFileSync(
104+
path.join(distDir, "manifest.json"),
105+
JSON.stringify(v1Manifest, null, 2),
106+
"utf-8"
107+
);
108+
109+
console.log("[MergeManifestPlugin] Manifest merged successfully");
110+
}
111+
);
112+
});
113+
114+
compiler.hooks.done.tap("MergeManifestPlugin", () => {
115+
console.log("\n[MergeManifestPlugin] Manifest merge finalized!\n");
116+
});
117+
}
118+
}
119+
120+
module.exports = MergeManifestPlugin;

0 commit comments

Comments
 (0)