@cartridge/controller 0.6.0 → 0.7.1
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/.turbo/turbo-build$colon$deps.log +52 -76
- package/.turbo/turbo-build.log +53 -77
- package/dist/controller.cjs +860 -0
- package/dist/controller.cjs.map +1 -0
- package/dist/controller.d.cts +33 -0
- package/dist/controller.d.ts +4 -4
- package/dist/controller.js +84 -68
- package/dist/controller.js.map +1 -1
- package/dist/index.cjs +2200 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +12 -7
- package/dist/index.js +161 -7601
- package/dist/index.js.map +1 -1
- package/dist/lookup.cjs +59 -0
- package/dist/lookup.cjs.map +1 -0
- package/dist/lookup.d.cts +4 -0
- package/dist/lookup.js +7 -7
- package/dist/lookup.js.map +1 -1
- package/dist/{provider.js → node/index.cjs} +499 -25
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +56 -0
- package/dist/node/index.d.ts +56 -0
- package/dist/{telegram/provider.js → node/index.js} +354 -135
- package/dist/node/index.js.map +1 -0
- package/dist/{policies.d.ts → policies-DD1aPjQ4.d.cts} +6 -4
- package/dist/policies-DD1aPjQ4.d.ts +21 -0
- package/dist/{types-CVnDQVqD.d.ts → provider-ap1C1ypF.d.cts} +27 -10
- package/dist/provider-ap1C1ypF.d.ts +201 -0
- package/dist/session/{provider.js → index.cjs} +82 -73
- package/dist/session/index.cjs.map +1 -0
- package/dist/session/{provider.d.ts → index.d.cts} +6 -3
- package/dist/session/index.d.ts +37 -8
- package/dist/session/index.js +47 -64
- package/dist/session/index.js.map +1 -1
- package/package.json +35 -16
- package/src/controller.ts +43 -15
- package/src/iframe/base.ts +1 -11
- package/src/node/account.ts +72 -0
- package/src/node/backend.ts +159 -0
- package/src/node/index.ts +4 -0
- package/src/node/provider.ts +178 -0
- package/src/node/server.ts +89 -0
- package/src/session/account.ts +1 -1
- package/src/session/provider.ts +0 -16
- package/src/types.ts +3 -6
- package/tsconfig.json +2 -1
- package/dist/__tests__/parseChainId.test.d.ts +0 -2
- package/dist/__tests__/parseChainId.test.js +0 -89
- package/dist/__tests__/parseChainId.test.js.map +0 -1
- package/dist/account.d.ts +0 -38
- package/dist/account.js +0 -110
- package/dist/account.js.map +0 -1
- package/dist/constants.d.ts +0 -5
- package/dist/constants.js +0 -10
- package/dist/constants.js.map +0 -1
- package/dist/errors.d.ts +0 -5
- package/dist/errors.js +0 -11
- package/dist/errors.js.map +0 -1
- package/dist/icon.d.ts +0 -3
- package/dist/icon.js +0 -6
- package/dist/icon.js.map +0 -1
- package/dist/iframe/base.d.ts +0 -5
- package/dist/iframe/base.js +0 -126
- package/dist/iframe/base.js.map +0 -1
- package/dist/iframe/index.d.ts +0 -5
- package/dist/iframe/index.js +0 -188
- package/dist/iframe/index.js.map +0 -1
- package/dist/iframe/keychain.d.ts +0 -5
- package/dist/iframe/keychain.js +0 -147
- package/dist/iframe/keychain.js.map +0 -1
- package/dist/iframe/profile.d.ts +0 -5
- package/dist/iframe/profile.js +0 -167
- package/dist/iframe/profile.js.map +0 -1
- package/dist/index.d-BbTUPBeO.d.ts +0 -68
- package/dist/mutex.d.ts +0 -14
- package/dist/mutex.js +0 -22
- package/dist/mutex.js.map +0 -1
- package/dist/policies.js +0 -26
- package/dist/policies.js.map +0 -1
- package/dist/provider.d.ts +0 -24
- package/dist/provider.js.map +0 -1
- package/dist/session/account.d.ts +0 -37
- package/dist/session/account.js +0 -94
- package/dist/session/account.js.map +0 -1
- package/dist/session/backend.d.ts +0 -60
- package/dist/session/backend.js +0 -39
- package/dist/session/backend.js.map +0 -1
- package/dist/session/provider.js.map +0 -1
- package/dist/telegram/backend.d.ts +0 -33
- package/dist/telegram/backend.js +0 -40
- package/dist/telegram/backend.js.map +0 -1
- package/dist/telegram/provider.d.ts +0 -27
- package/dist/telegram/provider.js.map +0 -1
- package/dist/types.d.ts +0 -5
- package/dist/types.js +0 -13
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -19
- package/dist/utils.js +0 -141
- package/dist/utils.js.map +0 -1
package/src/controller.ts
CHANGED
|
@@ -88,9 +88,11 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
88
88
|
|
|
89
89
|
const response = (await this.keychain.probe(this.rpcUrl())) as ProbeReply;
|
|
90
90
|
|
|
91
|
+
// For backwards compat with controller <=0.6.0
|
|
92
|
+
let rpcUrl = response?.rpcUrl || this.rpcUrl();
|
|
91
93
|
this.account = new ControllerAccount(
|
|
92
94
|
this,
|
|
93
|
-
|
|
95
|
+
rpcUrl,
|
|
94
96
|
response.address,
|
|
95
97
|
this.keychain,
|
|
96
98
|
this.options,
|
|
@@ -170,12 +172,20 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
async switchStarknetChain(chainId: string): Promise<boolean> {
|
|
175
|
+
if (!this.keychain || !this.iframes.keychain) {
|
|
176
|
+
console.error(new NotReadyToConnect().message);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
173
180
|
try {
|
|
174
181
|
this.selectedChain = chainId;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
const response = (await this.keychain.probe(this.rpcUrl())) as ProbeReply;
|
|
183
|
+
|
|
184
|
+
if (response.rpcUrl === this.rpcUrl()) {
|
|
185
|
+
return true;
|
|
178
186
|
}
|
|
187
|
+
|
|
188
|
+
await this.keychain.switchChain(this.rpcUrl());
|
|
179
189
|
} catch (e) {
|
|
180
190
|
console.error(e);
|
|
181
191
|
return false;
|
|
@@ -220,6 +230,34 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
220
230
|
this.iframes.profile.open();
|
|
221
231
|
}
|
|
222
232
|
|
|
233
|
+
async openProfileTo(to: string) {
|
|
234
|
+
if (!this.profile || !this.iframes.profile?.url) {
|
|
235
|
+
console.error("Profile is not ready");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!this.account) {
|
|
239
|
+
console.error("Account is not ready");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.profile.navigate(`${this.iframes.profile.url?.pathname}/${to}`);
|
|
244
|
+
this.iframes.profile.open();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async openProfileAt(at: string) {
|
|
248
|
+
if (!this.profile || !this.iframes.profile?.url) {
|
|
249
|
+
console.error("Profile is not ready");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!this.account) {
|
|
253
|
+
console.error("Account is not ready");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.profile.navigate(at);
|
|
258
|
+
this.iframes.profile.open();
|
|
259
|
+
}
|
|
260
|
+
|
|
223
261
|
async openSettings() {
|
|
224
262
|
if (!this.keychain || !this.iframes.keychain) {
|
|
225
263
|
console.error(new NotReadyToConnect().message);
|
|
@@ -262,16 +300,6 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
262
300
|
return this.keychain.username();
|
|
263
301
|
}
|
|
264
302
|
|
|
265
|
-
fetchControllers(
|
|
266
|
-
contractAddresses: string[],
|
|
267
|
-
): Promise<Record<string, string>> {
|
|
268
|
-
if (!this.keychain) {
|
|
269
|
-
throw new NotReadyToConnect().message;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return this.keychain.fetchControllers(contractAddresses);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
303
|
openPurchaseCredits() {
|
|
276
304
|
if (!this.keychain || !this.iframes.keychain) {
|
|
277
305
|
console.error(new NotReadyToConnect().message);
|
|
@@ -331,7 +359,7 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
331
359
|
}
|
|
332
360
|
|
|
333
361
|
private waitForKeychain({
|
|
334
|
-
timeout =
|
|
362
|
+
timeout = 50000,
|
|
335
363
|
interval = 100,
|
|
336
364
|
}:
|
|
337
365
|
| {
|
package/src/iframe/base.ts
CHANGED
|
@@ -19,12 +19,10 @@ export class IFrame<CallSender extends {}> implements Modal {
|
|
|
19
19
|
id,
|
|
20
20
|
url,
|
|
21
21
|
preset,
|
|
22
|
-
theme,
|
|
23
|
-
colorMode,
|
|
24
22
|
onClose,
|
|
25
23
|
onConnect,
|
|
26
24
|
methods = {},
|
|
27
|
-
}: Pick<ControllerOptions, "
|
|
25
|
+
}: Pick<ControllerOptions, "preset"> & {
|
|
28
26
|
id: string;
|
|
29
27
|
url: URL;
|
|
30
28
|
onClose?: () => void;
|
|
@@ -35,18 +33,10 @@ export class IFrame<CallSender extends {}> implements Modal {
|
|
|
35
33
|
return;
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
if (theme) {
|
|
39
|
-
url.searchParams.set("theme", theme);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
36
|
if (preset) {
|
|
43
37
|
url.searchParams.set("preset", preset);
|
|
44
38
|
}
|
|
45
39
|
|
|
46
|
-
if (colorMode) {
|
|
47
|
-
url.searchParams.set("colorMode", colorMode);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
40
|
this.url = url;
|
|
51
41
|
|
|
52
42
|
const iframe = document.createElement("iframe");
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Policy } from "@cartridge/account-wasm";
|
|
2
|
+
import { CartridgeSessionAccount } from "@cartridge/account-wasm/session";
|
|
3
|
+
import { Call, InvokeFunctionResponse, WalletAccount } from "starknet";
|
|
4
|
+
|
|
5
|
+
import { normalizeCalls } from "../utils";
|
|
6
|
+
import BaseProvider from "../provider";
|
|
7
|
+
|
|
8
|
+
export * from "../errors";
|
|
9
|
+
export * from "../types";
|
|
10
|
+
|
|
11
|
+
export default class SessionAccount extends WalletAccount {
|
|
12
|
+
public controller: CartridgeSessionAccount;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
provider: BaseProvider,
|
|
16
|
+
{
|
|
17
|
+
rpcUrl,
|
|
18
|
+
privateKey,
|
|
19
|
+
address,
|
|
20
|
+
ownerGuid,
|
|
21
|
+
chainId,
|
|
22
|
+
expiresAt,
|
|
23
|
+
policies,
|
|
24
|
+
}: {
|
|
25
|
+
rpcUrl: string;
|
|
26
|
+
privateKey: string;
|
|
27
|
+
address: string;
|
|
28
|
+
ownerGuid: string;
|
|
29
|
+
chainId: string;
|
|
30
|
+
expiresAt: number;
|
|
31
|
+
policies: Policy[];
|
|
32
|
+
},
|
|
33
|
+
) {
|
|
34
|
+
super({ nodeUrl: rpcUrl }, provider);
|
|
35
|
+
|
|
36
|
+
this.address = address;
|
|
37
|
+
this.controller = CartridgeSessionAccount.newAsRegistered(
|
|
38
|
+
rpcUrl,
|
|
39
|
+
privateKey,
|
|
40
|
+
address,
|
|
41
|
+
ownerGuid,
|
|
42
|
+
chainId,
|
|
43
|
+
{
|
|
44
|
+
expiresAt,
|
|
45
|
+
policies,
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Invoke execute function in account contract
|
|
52
|
+
*
|
|
53
|
+
* @param calls the invocation object or an array of them, containing:
|
|
54
|
+
* - contractAddress - the address of the contract
|
|
55
|
+
* - entrypoint - the entrypoint of the contract
|
|
56
|
+
* - calldata - (defaults to []) the calldata
|
|
57
|
+
* - signature - (defaults to []) the signature
|
|
58
|
+
*
|
|
59
|
+
* @returns response from addTransaction
|
|
60
|
+
*/
|
|
61
|
+
async execute(calls: Call | Call[]): Promise<InvokeFunctionResponse> {
|
|
62
|
+
try {
|
|
63
|
+
const res = await this.controller.executeFromOutside(
|
|
64
|
+
normalizeCalls(calls),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return res;
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return this.controller.execute(normalizeCalls(calls));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { CallbackServer } from "./server";
|
|
4
|
+
|
|
5
|
+
interface SessionSigner {
|
|
6
|
+
privKey: string;
|
|
7
|
+
pubKey: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SessionInfo {
|
|
11
|
+
username: string;
|
|
12
|
+
address: string;
|
|
13
|
+
ownerGuid: string;
|
|
14
|
+
transactionHash?: string;
|
|
15
|
+
expiresAt: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SessionData {
|
|
19
|
+
signer?: SessionSigner;
|
|
20
|
+
session?: SessionInfo;
|
|
21
|
+
policies?: any;
|
|
22
|
+
lastUsedConnector?: string;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Implements a file system backend.
|
|
28
|
+
* This is designed for Node.js environments to store session data on the filesystem.
|
|
29
|
+
*/
|
|
30
|
+
export class NodeBackend {
|
|
31
|
+
private basePath: string;
|
|
32
|
+
private sessionFile: string;
|
|
33
|
+
private data: SessionData = {};
|
|
34
|
+
private callbackServer?: CallbackServer;
|
|
35
|
+
|
|
36
|
+
constructor(basePath: string) {
|
|
37
|
+
if (!basePath) {
|
|
38
|
+
throw new Error("basePath is required for NodeBackend");
|
|
39
|
+
}
|
|
40
|
+
this.basePath = basePath;
|
|
41
|
+
this.sessionFile = path.join(this.basePath, "session.json");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async ensureDirectoryExists(): Promise<void> {
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(this.basePath);
|
|
47
|
+
} catch {
|
|
48
|
+
try {
|
|
49
|
+
await fs.mkdir(this.basePath, { recursive: true });
|
|
50
|
+
} catch (error: any) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Failed to create directory ${this.basePath}: ${error.message}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async loadData(): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
const content = await fs.readFile(this.sessionFile, "utf-8");
|
|
61
|
+
const parsed = JSON.parse(content);
|
|
62
|
+
|
|
63
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
64
|
+
throw new Error("Invalid session data format");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.data = parsed;
|
|
68
|
+
} catch (error: unknown) {
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
71
|
+
throw new Error(`Failed to load session data: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.data = {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async saveData(): Promise<void> {
|
|
79
|
+
try {
|
|
80
|
+
await this.ensureDirectoryExists();
|
|
81
|
+
await fs.writeFile(
|
|
82
|
+
this.sessionFile,
|
|
83
|
+
JSON.stringify(this.data, null, 2),
|
|
84
|
+
"utf-8",
|
|
85
|
+
);
|
|
86
|
+
} catch (error: unknown) {
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
throw new Error(`Failed to save session data: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async get(key: string): Promise<string | null> {
|
|
95
|
+
if (!key) {
|
|
96
|
+
throw new Error("Key is required");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await this.loadData();
|
|
100
|
+
return this.data[key] ? JSON.stringify(this.data[key]) : null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async set(key: string, value: string): Promise<void> {
|
|
104
|
+
if (!key) {
|
|
105
|
+
throw new Error("Key is required");
|
|
106
|
+
}
|
|
107
|
+
if (!value) {
|
|
108
|
+
throw new Error("Value is required");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await this.loadData();
|
|
112
|
+
try {
|
|
113
|
+
this.data[key] = JSON.parse(value);
|
|
114
|
+
await this.saveData();
|
|
115
|
+
} catch (error: unknown) {
|
|
116
|
+
if (error instanceof Error) {
|
|
117
|
+
throw new Error(`Failed to set ${key}: ${error.message}`);
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async delete(key: string): Promise<void> {
|
|
124
|
+
if (!key) {
|
|
125
|
+
throw new Error("Key is required");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await this.loadData();
|
|
129
|
+
delete this.data[key];
|
|
130
|
+
await this.saveData();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getRedirectUri(): Promise<string> {
|
|
134
|
+
try {
|
|
135
|
+
this.callbackServer = new CallbackServer();
|
|
136
|
+
return await this.callbackServer.listen();
|
|
137
|
+
} catch (error: unknown) {
|
|
138
|
+
if (error instanceof Error) {
|
|
139
|
+
throw new Error(`Failed to start callback server: ${error.message}`);
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async waitForCallback(): Promise<string | null> {
|
|
146
|
+
if (!this.callbackServer) {
|
|
147
|
+
throw new Error("Callback server not initialized");
|
|
148
|
+
}
|
|
149
|
+
return await this.callbackServer.waitForCallback();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
openLink(url: string): void {
|
|
153
|
+
if (!url) {
|
|
154
|
+
throw new Error("URL is required");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`\n\t Open url to authorize session: ${url}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { ec, stark, WalletAccount } from "starknet";
|
|
2
|
+
import { SessionPolicies } from "@cartridge/presets";
|
|
3
|
+
import { AddStarknetChainParameters } from "@starknet-io/types-js";
|
|
4
|
+
|
|
5
|
+
import SessionAccount from "./account";
|
|
6
|
+
import { KEYCHAIN_URL } from "../constants";
|
|
7
|
+
import BaseProvider from "../provider";
|
|
8
|
+
import { toWasmPolicies } from "../utils";
|
|
9
|
+
import { ParsedSessionPolicies } from "../policies";
|
|
10
|
+
import { NodeBackend } from "./backend";
|
|
11
|
+
|
|
12
|
+
export type SessionOptions = {
|
|
13
|
+
rpc: string;
|
|
14
|
+
chainId: string;
|
|
15
|
+
policies: SessionPolicies;
|
|
16
|
+
basePath: string;
|
|
17
|
+
keychainUrl?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default class SessionProvider extends BaseProvider {
|
|
21
|
+
public id = "controller_session";
|
|
22
|
+
public name = "Controller Session";
|
|
23
|
+
|
|
24
|
+
protected _chainId: string;
|
|
25
|
+
protected _rpcUrl: string;
|
|
26
|
+
protected _username?: string;
|
|
27
|
+
protected _policies: ParsedSessionPolicies;
|
|
28
|
+
protected _keychainUrl: string;
|
|
29
|
+
protected _backend: NodeBackend;
|
|
30
|
+
|
|
31
|
+
constructor({
|
|
32
|
+
rpc,
|
|
33
|
+
chainId,
|
|
34
|
+
policies,
|
|
35
|
+
basePath,
|
|
36
|
+
keychainUrl,
|
|
37
|
+
}: SessionOptions) {
|
|
38
|
+
super();
|
|
39
|
+
|
|
40
|
+
this._policies = {
|
|
41
|
+
verified: false,
|
|
42
|
+
contracts: policies.contracts
|
|
43
|
+
? Object.fromEntries(
|
|
44
|
+
Object.entries(policies.contracts).map(([address, contract]) => [
|
|
45
|
+
address,
|
|
46
|
+
{
|
|
47
|
+
...contract,
|
|
48
|
+
methods: contract.methods.map((method) => ({
|
|
49
|
+
...method,
|
|
50
|
+
authorized: true,
|
|
51
|
+
})),
|
|
52
|
+
},
|
|
53
|
+
]),
|
|
54
|
+
)
|
|
55
|
+
: undefined,
|
|
56
|
+
messages: policies.messages?.map((message) => ({
|
|
57
|
+
...message,
|
|
58
|
+
authorized: true,
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
this._rpcUrl = rpc;
|
|
63
|
+
this._chainId = chainId;
|
|
64
|
+
this._keychainUrl = keychainUrl || KEYCHAIN_URL;
|
|
65
|
+
this._backend = new NodeBackend(basePath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async username() {
|
|
69
|
+
const sessionStr = await this._backend.get("session");
|
|
70
|
+
if (sessionStr) {
|
|
71
|
+
const session = JSON.parse(sessionStr);
|
|
72
|
+
return session.username;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async probe(): Promise<WalletAccount | undefined> {
|
|
78
|
+
if (this.account) {
|
|
79
|
+
return this.account;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const [sessionStr, signerStr] = await Promise.all([
|
|
83
|
+
this._backend.get("session"),
|
|
84
|
+
this._backend.get("signer"),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
if (!sessionStr || !signerStr) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const session = JSON.parse(sessionStr);
|
|
92
|
+
const signer = JSON.parse(signerStr);
|
|
93
|
+
|
|
94
|
+
// Check expiration
|
|
95
|
+
const expirationTime = parseInt(session.expiresAt) * 1000;
|
|
96
|
+
if (Date.now() >= expirationTime) {
|
|
97
|
+
await this.disconnect();
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this._username = session.username;
|
|
102
|
+
this.account = new SessionAccount(this, {
|
|
103
|
+
rpcUrl: this._rpcUrl,
|
|
104
|
+
privateKey: signer.privKey,
|
|
105
|
+
address: session.address,
|
|
106
|
+
ownerGuid: session.ownerGuid,
|
|
107
|
+
chainId: this._chainId,
|
|
108
|
+
expiresAt: parseInt(session.expiresAt),
|
|
109
|
+
policies: toWasmPolicies(this._policies),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return this.account;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async connect(): Promise<WalletAccount | undefined> {
|
|
116
|
+
if (this.account) {
|
|
117
|
+
return this.account;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const account = await this.probe();
|
|
121
|
+
if (account) {
|
|
122
|
+
return account;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const pk = stark.randomAddress();
|
|
126
|
+
const publicKey = ec.starkCurve.getStarkKey(pk);
|
|
127
|
+
|
|
128
|
+
await this._backend.set(
|
|
129
|
+
"signer",
|
|
130
|
+
JSON.stringify({
|
|
131
|
+
privKey: pk,
|
|
132
|
+
pubKey: publicKey,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Get redirect URI from local server
|
|
137
|
+
const redirectUri = await this._backend.getRedirectUri();
|
|
138
|
+
|
|
139
|
+
const url = `${
|
|
140
|
+
this._keychainUrl
|
|
141
|
+
}/session?public_key=${encodeURIComponent(publicKey)}&redirect_uri=${encodeURIComponent(
|
|
142
|
+
redirectUri,
|
|
143
|
+
)}&redirect_query_name=startapp&policies=${encodeURIComponent(
|
|
144
|
+
JSON.stringify(this._policies),
|
|
145
|
+
)}&rpc_url=${encodeURIComponent(this._rpcUrl)}`;
|
|
146
|
+
|
|
147
|
+
this._backend.openLink(url);
|
|
148
|
+
|
|
149
|
+
// Wait for callback with session data
|
|
150
|
+
const sessionData = await this._backend.waitForCallback();
|
|
151
|
+
if (sessionData) {
|
|
152
|
+
const sessionRegistration = JSON.parse(atob(sessionData));
|
|
153
|
+
// Ensure addresses are properly formatted
|
|
154
|
+
sessionRegistration.address = sessionRegistration.address.toLowerCase();
|
|
155
|
+
sessionRegistration.ownerGuid =
|
|
156
|
+
sessionRegistration.ownerGuid.toLowerCase();
|
|
157
|
+
await this._backend.set("session", JSON.stringify(sessionRegistration));
|
|
158
|
+
return this.probe();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async disconnect(): Promise<void> {
|
|
165
|
+
await this._backend.delete("signer");
|
|
166
|
+
await this._backend.delete("session");
|
|
167
|
+
this.account = undefined;
|
|
168
|
+
this._username = undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
switchStarknetChain(_chainId: string): Promise<boolean> {
|
|
172
|
+
throw new Error("switchStarknetChain not implemented");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
addStarknetChain(_chain: AddStarknetChainParameters): Promise<boolean> {
|
|
176
|
+
throw new Error("addStarknetChain not implemented");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as http from "http";
|
|
2
|
+
import { AddressInfo } from "net";
|
|
3
|
+
|
|
4
|
+
type ServerResponse = http.ServerResponse<http.IncomingMessage> & {
|
|
5
|
+
req: http.IncomingMessage;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class CallbackServer {
|
|
9
|
+
private server: http.Server;
|
|
10
|
+
private resolveCallback?: (data: string) => void;
|
|
11
|
+
private rejectCallback?: (error: Error) => void;
|
|
12
|
+
private timeoutId?: NodeJS.Timeout;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.server = http.createServer(this.handleRequest.bind(this));
|
|
16
|
+
|
|
17
|
+
// Handle server errors
|
|
18
|
+
this.server.on("error", (error) => {
|
|
19
|
+
console.error("Server error:", error);
|
|
20
|
+
if (this.rejectCallback) {
|
|
21
|
+
this.rejectCallback(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private cleanup() {
|
|
27
|
+
if (this.timeoutId) {
|
|
28
|
+
clearTimeout(this.timeoutId);
|
|
29
|
+
}
|
|
30
|
+
this.server.close();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private handleRequest(req: http.IncomingMessage, res: ServerResponse) {
|
|
34
|
+
if (!req.url?.startsWith("/callback")) {
|
|
35
|
+
res.writeHead(404);
|
|
36
|
+
res.end();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const params = new URLSearchParams(req.url.split("?")[1]);
|
|
41
|
+
const session = params.get("startapp");
|
|
42
|
+
|
|
43
|
+
if (!session) {
|
|
44
|
+
console.warn("Received callback without session data");
|
|
45
|
+
res.writeHead(400);
|
|
46
|
+
res.end("Missing session data");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.resolveCallback) {
|
|
51
|
+
this.resolveCallback(session);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
55
|
+
res.end(
|
|
56
|
+
"<html><body><script>window.close();</script>Session registered successfully. You can close this window.</body></html>",
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
this.cleanup();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async listen(): Promise<string> {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
this.server.listen(0, "localhost", () => {
|
|
65
|
+
const address = this.server.address() as AddressInfo;
|
|
66
|
+
const url = `http://localhost:${address.port}/callback`;
|
|
67
|
+
resolve(url);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.server.on("error", reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async waitForCallback(): Promise<string> {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
this.resolveCallback = resolve;
|
|
77
|
+
this.rejectCallback = reject;
|
|
78
|
+
|
|
79
|
+
this.timeoutId = setTimeout(
|
|
80
|
+
() => {
|
|
81
|
+
console.warn("Callback timeout reached");
|
|
82
|
+
reject(new Error("Callback timeout after 5 minutes"));
|
|
83
|
+
this.cleanup();
|
|
84
|
+
},
|
|
85
|
+
5 * 60 * 1000,
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/session/account.ts
CHANGED
|
@@ -34,7 +34,7 @@ export default class SessionAccount extends WalletAccount {
|
|
|
34
34
|
super({ nodeUrl: rpcUrl }, provider);
|
|
35
35
|
|
|
36
36
|
this.address = address;
|
|
37
|
-
this.controller = CartridgeSessionAccount.
|
|
37
|
+
this.controller = CartridgeSessionAccount.newAsRegistered(
|
|
38
38
|
rpcUrl,
|
|
39
39
|
privateKey,
|
|
40
40
|
address,
|
package/src/session/provider.ts
CHANGED
|
@@ -219,23 +219,13 @@ export default class SessionProvider extends BaseProvider {
|
|
|
219
219
|
|
|
220
220
|
// Check expiration
|
|
221
221
|
const expirationTime = parseInt(sessionRegistration.expiresAt) * 1000;
|
|
222
|
-
console.log("Session expiration check:", {
|
|
223
|
-
expirationTime,
|
|
224
|
-
currentTime: Date.now(),
|
|
225
|
-
expired: Date.now() >= expirationTime,
|
|
226
|
-
});
|
|
227
222
|
if (Date.now() >= expirationTime) {
|
|
228
|
-
console.log("Session expired, clearing stored session");
|
|
229
223
|
this.clearStoredSession();
|
|
230
224
|
return;
|
|
231
225
|
}
|
|
232
226
|
|
|
233
227
|
// Check stored policies
|
|
234
228
|
const storedPoliciesStr = localStorage.getItem("sessionPolicies");
|
|
235
|
-
console.log("Checking stored policies:", {
|
|
236
|
-
storedPoliciesStr,
|
|
237
|
-
currentPolicies: this._policies,
|
|
238
|
-
});
|
|
239
229
|
if (storedPoliciesStr) {
|
|
240
230
|
const storedPolicies = JSON.parse(
|
|
241
231
|
storedPoliciesStr,
|
|
@@ -245,14 +235,8 @@ export default class SessionProvider extends BaseProvider {
|
|
|
245
235
|
this._policies,
|
|
246
236
|
storedPolicies,
|
|
247
237
|
);
|
|
248
|
-
console.log("Policy validation result:", {
|
|
249
|
-
isValid,
|
|
250
|
-
storedPolicies,
|
|
251
|
-
requestedPolicies: this._policies,
|
|
252
|
-
});
|
|
253
238
|
|
|
254
239
|
if (!isValid) {
|
|
255
|
-
console.log("Policy validation failed, clearing stored session");
|
|
256
240
|
this.clearStoredSession();
|
|
257
241
|
return;
|
|
258
242
|
}
|