Skip to content

Commit dd0ef56

Browse files
committed
wip popup
1 parent a82b9ad commit dd0ef56

7 files changed

Lines changed: 187 additions & 83 deletions

File tree

packages/entrykit/src/ConnectWallet.tsx

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@ import { useModal } from "connectkit";
33
import { AppInfo } from "./AppInfo";
44
import { twMerge } from "tailwind-merge";
55
import { useEffect, useRef } from "react";
6-
import { Connector, useCapabilities, useConnect, useConnectors } from "wagmi";
7-
import { isIdConnector, PortoConnector, type IdConnector } from "@latticexyz/id/internal";
6+
import { useConnect, useConnectors } from "wagmi";
7+
import { PortoConnector } from "@latticexyz/id/internal";
88

99
export function ConnectWallet() {
1010
const connectors = useConnectors();
1111
const porto = connectors.find((connector): connector is PortoConnector => connector.id === "xyz.ithaca.porto");
12-
const capabilities = useCapabilities();
13-
14-
console.log("capabilities", capabilities.status, capabilities.data, capabilities.error);
15-
const idConnector = connectors.find(isIdConnector);
1612

1713
// TODO: show error states?
1814

@@ -25,12 +21,38 @@ export function ConnectWallet() {
2521
<AppInfo />
2622
</div>
2723
<div className="self-center flex flex-col gap-2 w-60">
28-
{porto ? <AccountButtons connector={porto} /> : <WalletButton />}
24+
{porto ? <AccountButton connector={porto} /> : <WalletButton />}
2925
</div>
3026
</div>
3127
);
3228
}
3329

30+
function AccountButton({ connector }: { connector: PortoConnector }) {
31+
const { setOpen } = useModal();
32+
const { connect, isPending } = useConnect();
33+
34+
return (
35+
<>
36+
<Button
37+
key="signin"
38+
variant="secondary"
39+
className="self-auto flex justify-center"
40+
pending={isPending}
41+
onClick={() => connect({ connector })}
42+
autoFocus
43+
>
44+
Sign in
45+
</Button>
46+
<button
47+
className="text-sm self-center transition text-neutral-500 hover:text-white p-2"
48+
onClick={() => setOpen(true)}
49+
>
50+
Already have a wallet?
51+
</button>
52+
</>
53+
);
54+
}
55+
3456
function WalletButton() {
3557
const { open, setOpen } = useModal();
3658
const hasAutoOpenedRef = useRef(false);
@@ -56,54 +78,3 @@ function WalletButton() {
5678
</>
5779
);
5880
}
59-
60-
function AccountButtons({ connector }: { connector: PortoConnector }) {
61-
const { setOpen } = useModal();
62-
const { connect, isPending } = useConnect();
63-
64-
// TODO: fill in once we can look out accounts/capabilities
65-
const signIn = false;
66-
67-
// TODO: these buttons don't work :(
68-
// > A user activation is required to create a credential in a cross-origin iframe
69-
70-
const buttons = [
71-
<Button
72-
key="create"
73-
variant={signIn ? "tertiary" : "secondary"}
74-
className="self-auto flex justify-center"
75-
pending={isPending}
76-
onClick={() => connect({ connector })}
77-
autoFocus={!signIn}
78-
>
79-
Create account
80-
</Button>,
81-
<Button
82-
key="signin"
83-
variant={signIn ? "secondary" : "tertiary"}
84-
className="self-auto flex justify-center"
85-
pending={isPending}
86-
// TODO: pass in credential ID to use
87-
onClick={() => connect({ connector })}
88-
autoFocus={signIn}
89-
>
90-
Sign in
91-
</Button>,
92-
];
93-
94-
if (signIn) {
95-
buttons.reverse();
96-
}
97-
98-
return (
99-
<>
100-
{buttons}
101-
<button
102-
className="text-sm self-center transition text-neutral-500 hover:text-white p-2"
103-
onClick={() => setOpen(true)}
104-
>
105-
Already have a wallet?
106-
</button>
107-
</>
108-
);
109-
}

packages/id/connect/index.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>id.place</title>
7+
<style>
8+
#hello {
9+
position: fixed;
10+
inset: 0;
11+
display: flex;
12+
flex-direction: column;
13+
align-items: center;
14+
justify-content: center;
15+
text-align: center;
16+
font-family: monospace;
17+
font-size: 1rem;
18+
line-height: 1.5;
19+
}
20+
#hello h1 {
21+
font-size: 2rem;
22+
margin: 0;
23+
}
24+
#hello p {
25+
font-size: 0.875rem;
26+
margin: 0;
27+
}
28+
</style>
29+
</head>
30+
<body>
31+
<div id="hello">
32+
<svg width="4rem" height="4rem" viewBox="0 0 24 24" fill="currentColor" shape-rendering="crispEdges">
33+
<path
34+
d="M2 3H0v18h24V3H2zm20 2v14H2V5h20zM10 7H6v4h4V7zm-6 6h8v4H4v-4zm16-6h-6v2h6V7zm-6 4h6v2h-6v-2zm6 4h-6v2h6v-2z"
35+
/>
36+
</svg>
37+
<h1>id.place</h1>
38+
<p>a place for your internet identity</p>
39+
</div>
40+
<script type="module" src="./src/rp/connect.ts"></script>
41+
</body>
42+
</html>

packages/id/src/client/openRp.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { connectMessagePort } from "../messagePort/connectMessagePort";
2+
import { connectUrl } from "../rp/common";
3+
import { type } from "arktype";
4+
5+
// TODO: preload this page and its assets by loading it in a background iframe
6+
7+
const frameContextShape = type({
8+
id: "string",
9+
origin: "string",
10+
});
11+
12+
let frameId = 0;
13+
14+
export function openRp(): {
15+
port: Promise<MessagePort>;
16+
destroy: () => void;
17+
} {
18+
const id = `${Date.now()}-${++frameId}`;
19+
const frameContext = frameContextShape.from({ id, origin: window.location.origin });
20+
21+
const url = new URL(connectUrl);
22+
url.hash = new URLSearchParams({ frame: JSON.stringify(frameContext) }).toString();
23+
24+
const width = 400;
25+
const height = 500;
26+
const left = window.screenX + (window.outerWidth - width) / 2;
27+
const top = window.screenY + (window.outerHeight - height) / 2;
28+
29+
const target = window.open(
30+
url.toString(),
31+
// TODO: decide if it would be better to share a window across all rp instances
32+
"_blank",
33+
`popup,width=${width},height=${height},left=${left},top=${top},resizable=yes`,
34+
);
35+
if (!target) throw new Error("could not open rp window");
36+
37+
const port = connectMessagePort({ id, target });
38+
39+
return {
40+
port,
41+
destroy: () => {
42+
port.then((p) => p.close());
43+
target.close();
44+
},
45+
};
46+
}
47+
48+
export function getFrameContext(url: string): typeof frameContextShape.infer {
49+
const parse = type("parse.json").to(frameContextShape);
50+
const hashParams = new URLSearchParams(new URL(url).hash.slice(1));
51+
const frameContext = parse(hashParams.get("frame"));
52+
if (frameContext instanceof type.errors) {
53+
throw new AggregateError(frameContext, `Could not parse frame context:\n\n${frameContext.toString()}`);
54+
}
55+
return frameContext;
56+
}

packages/id/src/porto/mode.ts

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,67 @@
1-
import { Mode, Account } from "porto";
2-
import { connectRp } from "../client/connectRp";
1+
import { Mode, Account, Dialog } from "porto";
2+
import { openRp } from "../client/openRp";
33
import { createTimeout } from "../createTimeout";
44
import { syncPort } from "../sync/syncPort";
55
import { sharedState } from "../sync/sharedState";
6+
import { connectUrl } from "../rp/common";
67

7-
const rp = connectRp();
8-
rp.port.then((port) => syncPort("rp", port));
8+
// const rp = connectRp();
9+
// rp.port.then((port) => syncPort("rp", port));
910

10-
async function createAccount() {
11-
const port = await rp.port;
12-
return await new Promise<Account.Account>((resolve, reject) => {
13-
function onPortMessage(event: MessageEvent) {
14-
if (event.data === "createResult") {
15-
console.log("got create result");
16-
resolve({} as never);
17-
}
18-
}
19-
port.addEventListener("message", onPortMessage);
11+
// async function createAccount() {
12+
// const port = await rp.port;
13+
// return await new Promise<Account.Account>((resolve, reject) => {
14+
// function onPortMessage(event: MessageEvent) {
15+
// if (event.data === "createResult") {
16+
// console.log("got create result");
17+
// resolve({} as never);
18+
// }
19+
// }
20+
// port.addEventListener("message", onPortMessage);
21+
22+
// const timeout = createTimeout(60_000);
23+
// timeout.promise.finally(() => port.removeEventListener("message", onPortMessage));
24+
// timeout.promise.catch(reject);
2025

21-
const timeout = createTimeout(60_000);
22-
timeout.promise.finally(() => port.removeEventListener("message", onPortMessage));
23-
timeout.promise.catch(reject);
26+
// console.log("asking port to create account");
27+
// port.postMessage("create");
28+
// });
29+
// }
2430

25-
console.log("asking port to create account");
26-
port.postMessage("create");
27-
});
28-
}
31+
const renderer = Dialog.popup();
2932

3033
export const mode = Mode.from({
3134
name: "rp",
35+
setup(params) {
36+
console.log("mode.setup()");
37+
const dialog = renderer.setup({
38+
host: connectUrl,
39+
internal: params.internal,
40+
});
41+
42+
const unsub = params.internal.store.subscribe(
43+
(x) => x.requestQueue,
44+
(requestQueue) => {
45+
for (const listener of listeners) listener(requestQueue);
46+
47+
const requests = requestQueue
48+
.map((x) => (x.status === "pending" ? x : undefined))
49+
.filter(Boolean) as readonly QueuedRequest[];
50+
dialog.syncRequests(requests);
51+
if (requests.length === 0) dialog.close();
52+
},
53+
);
54+
55+
return () => {
56+
dialog.destroy();
57+
};
58+
},
3259
actions: {
3360
async createAccount() {
3461
console.log("mode.actions.createAccount()");
35-
const account = await createAccount();
36-
return { account };
62+
throw new Error("not implemented");
63+
// const account = await createAccount();
64+
// return { account: Account.from({}) };
3765
},
3866

3967
async loadAccounts() {
@@ -43,7 +71,10 @@ export const mode = Mode.from({
4371

4472
if (!accounts.length) {
4573
console.log("create account");
46-
await createAccount();
74+
const rp = openRp();
75+
await rp.port;
76+
77+
// await createAccount();
4778
throw new Error("failed");
4879
} else {
4980
console.log("TODO sign in");

packages/id/src/rp/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export const rp = {
44
} as const;
55

66
export const rpUrl = "https://id.smartpass.dev";
7+
// needs trailing slash
8+
export const connectUrl = "https://id.smartpass.dev/connect/";

packages/id/src/rp/connect.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("hello from connect");

packages/id/src/rp/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async function connectClient() {
4141

4242
port.addEventListener("message", async (event) => {
4343
if (event.data === "create") {
44+
console.log("got create request", navigator.userActivation);
4445
try {
4546
await create();
4647
port.postMessage("createResult");
@@ -76,7 +77,7 @@ async function create() {
7677
// debug("credential from localStorage", id);
7778
// if (id) return id;
7879

79-
debug("creating credential");
80+
debug("creating credential", navigator.userActivation);
8081
const credential = await WebAuthnP256.createCredential({
8182
rp,
8283
user: {

0 commit comments

Comments
 (0)