@cartridge/controller 0.5.9 → 0.7.0
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 +53 -115
- package/.turbo/turbo-build.log +54 -116
- 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 +254 -170
- 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 -6
- package/dist/index.js +296 -7666
- 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/node/index.cjs +726 -0
- 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} +521 -216
- package/dist/node/index.js.map +1 -0
- package/dist/policies-DD1aPjQ4.d.cts +21 -0
- 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} +342 -168
- package/dist/session/index.cjs.map +1 -0
- package/dist/session/index.d.cts +38 -0
- package/dist/session/index.d.ts +37 -7
- package/dist/session/index.js +309 -161
- package/dist/session/index.js.map +1 -1
- package/package.json +49 -16
- package/src/controller.ts +44 -15
- package/src/iframe/base.ts +1 -11
- package/src/mutex.ts +22 -0
- 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/policies.ts +49 -0
- package/src/provider.ts +33 -2
- package/src/session/account.ts +2 -1
- package/src/session/provider.ts +123 -10
- package/src/telegram/provider.ts +3 -2
- package/src/types.ts +3 -6
- package/src/utils.ts +4 -1
- package/tsconfig.json +3 -3
- 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 -106
- 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 -122
- package/dist/iframe/base.js.map +0 -1
- package/dist/iframe/index.d.ts +0 -5
- package/dist/iframe/index.js +0 -184
- package/dist/iframe/index.js.map +0 -1
- package/dist/iframe/keychain.d.ts +0 -5
- package/dist/iframe/keychain.js +0 -143
- package/dist/iframe/keychain.js.map +0 -1
- package/dist/iframe/profile.d.ts +0 -5
- package/dist/iframe/profile.js +0 -163
- package/dist/iframe/profile.js.map +0 -1
- package/dist/index.d-BbTUPBeO.d.ts +0 -68
- package/dist/provider.d.ts +0 -22
- package/dist/provider.js +0 -198
- package/dist/provider.js.map +0 -1
- package/dist/session/account.d.ts +0 -37
- package/dist/session/account.js +0 -92
- 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.d.ts +0 -30
- 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 -26
- 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 -18
- package/dist/utils.js +0 -139
- package/dist/utils.js.map +0 -1
|
@@ -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/policies.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContractPolicy,
|
|
3
|
+
Method,
|
|
4
|
+
SessionPolicies,
|
|
5
|
+
SignMessagePolicy,
|
|
6
|
+
} from "@cartridge/presets";
|
|
7
|
+
|
|
8
|
+
export type ParsedSessionPolicies = {
|
|
9
|
+
verified: boolean;
|
|
10
|
+
contracts?: SessionContracts;
|
|
11
|
+
messages?: SessionMessages;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type SessionContracts = Record<
|
|
15
|
+
string,
|
|
16
|
+
Omit<ContractPolicy, "methods"> & {
|
|
17
|
+
methods: (Method & { authorized?: boolean })[];
|
|
18
|
+
}
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export type SessionMessages = (SignMessagePolicy & {
|
|
22
|
+
authorized?: boolean;
|
|
23
|
+
})[];
|
|
24
|
+
|
|
25
|
+
export function parsePolicies(
|
|
26
|
+
policies: SessionPolicies,
|
|
27
|
+
): ParsedSessionPolicies {
|
|
28
|
+
return {
|
|
29
|
+
verified: false,
|
|
30
|
+
contracts: policies.contracts
|
|
31
|
+
? Object.fromEntries(
|
|
32
|
+
Object.entries(policies.contracts).map(([address, contract]) => [
|
|
33
|
+
address,
|
|
34
|
+
{
|
|
35
|
+
...contract,
|
|
36
|
+
methods: contract.methods.map((method) => ({
|
|
37
|
+
...method,
|
|
38
|
+
authorized: true,
|
|
39
|
+
})),
|
|
40
|
+
},
|
|
41
|
+
]),
|
|
42
|
+
)
|
|
43
|
+
: undefined,
|
|
44
|
+
messages: policies.messages?.map((message) => ({
|
|
45
|
+
...message,
|
|
46
|
+
authorized: true,
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
}
|
package/src/provider.ts
CHANGED
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
import manifest from "../package.json";
|
|
17
17
|
|
|
18
18
|
import { icon } from "./icon";
|
|
19
|
+
import { Mutex } from "./mutex";
|
|
20
|
+
|
|
21
|
+
const mutex = new Mutex();
|
|
19
22
|
|
|
20
23
|
export default abstract class BaseProvider implements StarknetWindowObject {
|
|
21
24
|
public id = "controller";
|
|
@@ -26,10 +29,37 @@ export default abstract class BaseProvider implements StarknetWindowObject {
|
|
|
26
29
|
public account?: WalletAccount;
|
|
27
30
|
public subscriptions: WalletEvents[] = [];
|
|
28
31
|
|
|
32
|
+
private _probePromise: Promise<WalletAccount | undefined> | null = null;
|
|
33
|
+
|
|
34
|
+
protected async safeProbe(): Promise<WalletAccount | undefined> {
|
|
35
|
+
// If we already have an account, return it
|
|
36
|
+
if (this.account) {
|
|
37
|
+
return this.account;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If we're already probing, wait for the existing probe
|
|
41
|
+
if (this._probePromise) {
|
|
42
|
+
return this._probePromise;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const release = await mutex.obtain();
|
|
46
|
+
return await new Promise<WalletAccount | undefined>(async (resolve) => {
|
|
47
|
+
try {
|
|
48
|
+
this._probePromise = this.probe();
|
|
49
|
+
const result = await this._probePromise;
|
|
50
|
+
resolve(result);
|
|
51
|
+
} finally {
|
|
52
|
+
this._probePromise = null;
|
|
53
|
+
}
|
|
54
|
+
}).finally(() => {
|
|
55
|
+
release();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
29
59
|
request: RequestFn = async (call) => {
|
|
30
60
|
switch (call.type) {
|
|
31
61
|
case "wallet_getPermissions":
|
|
32
|
-
await this.
|
|
62
|
+
await this.safeProbe();
|
|
33
63
|
|
|
34
64
|
if (this.account) {
|
|
35
65
|
return [Permission.ACCOUNTS];
|
|
@@ -45,7 +75,8 @@ export default abstract class BaseProvider implements StarknetWindowObject {
|
|
|
45
75
|
const silentMode =
|
|
46
76
|
call.params && (call.params as RequestAccountsParameters).silent_mode;
|
|
47
77
|
|
|
48
|
-
this.account = await this.
|
|
78
|
+
this.account = await this.safeProbe();
|
|
79
|
+
|
|
49
80
|
if (!this.account && !silentMode) {
|
|
50
81
|
this.account = await this.connect();
|
|
51
82
|
}
|
package/src/session/account.ts
CHANGED
|
@@ -33,7 +33,8 @@ export default class SessionAccount extends WalletAccount {
|
|
|
33
33
|
) {
|
|
34
34
|
super({ nodeUrl: rpcUrl }, provider);
|
|
35
35
|
|
|
36
|
-
this.
|
|
36
|
+
this.address = address;
|
|
37
|
+
this.controller = CartridgeSessionAccount.newAsRegistered(
|
|
37
38
|
rpcUrl,
|
|
38
39
|
privateKey,
|
|
39
40
|
address,
|