@cartridge/controller 0.3.46 → 0.4.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 +4 -0
- package/dist/{index.d.ts → controller.d.ts} +2 -3
- package/dist/{index.js → controller.js} +7 -7
- package/dist/controller.js.map +1 -0
- package/dist/iframe/profile.d.ts +2 -1
- package/dist/presets.js +9 -0
- package/dist/presets.js.map +1 -1
- package/dist/session.d.ts +40 -0
- package/dist/session.js +44 -0
- package/dist/session.js.map +1 -0
- package/dist/signer.d.ts +27 -1
- package/dist/signer.js +35 -0
- package/dist/signer.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/utils.d.ts +6 -1
- package/dist/utils.js +10 -23
- package/dist/utils.js.map +1 -1
- package/package.json +19 -8
- package/src/constants.ts +4 -0
- package/src/controller.ts +271 -0
- package/src/device.ts +162 -0
- package/src/errors.ts +15 -0
- package/src/iframe/base.ts +140 -0
- package/src/iframe/index.ts +3 -0
- package/src/iframe/keychain.ts +34 -0
- package/src/iframe/profile.ts +42 -0
- package/src/presets.ts +140 -0
- package/src/session.ts +92 -0
- package/src/signer.ts +144 -0
- package/src/types.ts +246 -0
- package/src/utils.ts +21 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/index.js.map +0 -1
- package/dist/verified.d.ts +0 -5
- package/dist/verified.js +0 -7
- package/dist/verified.js.map +0 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { AccountInterface, addAddressPadding } from "starknet";
|
|
2
|
+
import { AsyncMethodReturns } from "@cartridge/penpal";
|
|
3
|
+
|
|
4
|
+
import DeviceAccount from "./device";
|
|
5
|
+
import {
|
|
6
|
+
Keychain,
|
|
7
|
+
Policy,
|
|
8
|
+
ResponseCodes,
|
|
9
|
+
ConnectReply,
|
|
10
|
+
ProbeReply,
|
|
11
|
+
ControllerOptions,
|
|
12
|
+
ConnectError,
|
|
13
|
+
Profile,
|
|
14
|
+
IFrames,
|
|
15
|
+
ProfileContextTypeVariant,
|
|
16
|
+
} from "./types";
|
|
17
|
+
import { KeychainIFrame, ProfileIFrame } from "./iframe";
|
|
18
|
+
import { NotReadyToConnect, ProfileNotReady } from "./errors";
|
|
19
|
+
import { RPC_SEPOLIA } from "./constants";
|
|
20
|
+
|
|
21
|
+
export * from "./errors";
|
|
22
|
+
export * from "./types";
|
|
23
|
+
export { defaultPresets } from "./presets";
|
|
24
|
+
|
|
25
|
+
export default class Controller {
|
|
26
|
+
private policies: Policy[];
|
|
27
|
+
private keychain?: AsyncMethodReturns<Keychain>;
|
|
28
|
+
private profile?: AsyncMethodReturns<Profile>;
|
|
29
|
+
private options: ControllerOptions;
|
|
30
|
+
private iframes: IFrames;
|
|
31
|
+
public rpc: URL;
|
|
32
|
+
public account?: AccountInterface;
|
|
33
|
+
|
|
34
|
+
constructor({
|
|
35
|
+
policies,
|
|
36
|
+
url,
|
|
37
|
+
rpc,
|
|
38
|
+
paymaster,
|
|
39
|
+
...options
|
|
40
|
+
}: ControllerOptions = {}) {
|
|
41
|
+
this.iframes = {
|
|
42
|
+
keychain: new KeychainIFrame({
|
|
43
|
+
...options,
|
|
44
|
+
url,
|
|
45
|
+
paymaster,
|
|
46
|
+
onClose: this.keychain?.reset,
|
|
47
|
+
onConnect: (keychain) => {
|
|
48
|
+
this.keychain = keychain;
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
this.rpc = new URL(rpc || RPC_SEPOLIA);
|
|
54
|
+
|
|
55
|
+
// TODO: remove this on the next major breaking change. pass everthing by url
|
|
56
|
+
this.policies =
|
|
57
|
+
policies?.map((policy) => ({
|
|
58
|
+
...policy,
|
|
59
|
+
target: addAddressPadding(policy.target),
|
|
60
|
+
})) || [];
|
|
61
|
+
|
|
62
|
+
this.options = options;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async openSettings() {
|
|
66
|
+
if (!this.keychain || !this.iframes.keychain) {
|
|
67
|
+
console.error(new NotReadyToConnect().message);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
this.iframes.keychain.open();
|
|
71
|
+
const res = await this.keychain.openSettings();
|
|
72
|
+
this.iframes.keychain.close();
|
|
73
|
+
if (res && (res as ConnectError).code === ResponseCodes.NOT_CONNECTED) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ready() {
|
|
80
|
+
return this.probe().then(
|
|
81
|
+
(res) => !!res,
|
|
82
|
+
() => false,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async probe() {
|
|
87
|
+
try {
|
|
88
|
+
await this.waitForKeychain();
|
|
89
|
+
|
|
90
|
+
if (!this.keychain) {
|
|
91
|
+
console.error(new NotReadyToConnect().message);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const response = (await this.keychain.probe(
|
|
96
|
+
this.rpc.toString(),
|
|
97
|
+
)) as ProbeReply;
|
|
98
|
+
|
|
99
|
+
this.account = new DeviceAccount(
|
|
100
|
+
this.rpc.toString(),
|
|
101
|
+
response.address,
|
|
102
|
+
this.keychain,
|
|
103
|
+
this.options,
|
|
104
|
+
this.iframes.keychain,
|
|
105
|
+
) as AccountInterface;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.log(e);
|
|
108
|
+
console.error(new NotReadyToConnect().message);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
this.options.profileUrl &&
|
|
114
|
+
this.options.indexerUrl &&
|
|
115
|
+
!this.iframes.profile
|
|
116
|
+
) {
|
|
117
|
+
const username = await this.keychain.username();
|
|
118
|
+
this.iframes.profile = new ProfileIFrame({
|
|
119
|
+
profileUrl: this.options.profileUrl,
|
|
120
|
+
indexerUrl: this.options.indexerUrl,
|
|
121
|
+
address: this.account.address,
|
|
122
|
+
username,
|
|
123
|
+
rpcUrl: this.rpc.toString(),
|
|
124
|
+
tokens: this.options.tokens,
|
|
125
|
+
onConnect: (profile) => {
|
|
126
|
+
this.profile = profile;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return !!this.account;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async connect() {
|
|
135
|
+
if (this.account) {
|
|
136
|
+
return this.account;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!this.keychain || !this.iframes.keychain) {
|
|
140
|
+
console.error(new NotReadyToConnect().message);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!!document.hasStorageAccess) {
|
|
145
|
+
const ok = await document.hasStorageAccess();
|
|
146
|
+
if (!ok) {
|
|
147
|
+
await document.requestStorageAccess();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.iframes.keychain.open();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
let response = await this.keychain.connect(
|
|
155
|
+
this.policies,
|
|
156
|
+
this.rpc.toString(),
|
|
157
|
+
);
|
|
158
|
+
if (response.code !== ResponseCodes.SUCCESS) {
|
|
159
|
+
throw new Error(response.message);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
response = response as ConnectReply;
|
|
163
|
+
this.account = new DeviceAccount(
|
|
164
|
+
this.rpc.toString(),
|
|
165
|
+
response.address,
|
|
166
|
+
this.keychain,
|
|
167
|
+
this.options,
|
|
168
|
+
this.iframes.keychain,
|
|
169
|
+
) as AccountInterface;
|
|
170
|
+
|
|
171
|
+
return this.account;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.log(e);
|
|
174
|
+
} finally {
|
|
175
|
+
this.iframes.keychain.close();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
openProfile(tab: ProfileContextTypeVariant = "inventory") {
|
|
180
|
+
if (!this.options.indexerUrl) {
|
|
181
|
+
console.error("`indexerUrl` option is required to open profile");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!this.profile || !this.iframes.profile) {
|
|
185
|
+
console.error(new ProfileNotReady().message);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.profile.navigate(tab);
|
|
190
|
+
this.iframes.profile.open();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async disconnect() {
|
|
194
|
+
if (!this.keychain) {
|
|
195
|
+
console.error(new NotReadyToConnect().message);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!!document.hasStorageAccess) {
|
|
200
|
+
const ok = await document.hasStorageAccess();
|
|
201
|
+
if (!ok) {
|
|
202
|
+
await document.requestStorageAccess();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.account = undefined;
|
|
207
|
+
return this.keychain.disconnect();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
revoke(origin: string, _policy: Policy[]) {
|
|
211
|
+
if (!this.keychain) {
|
|
212
|
+
console.error(new NotReadyToConnect().message);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return this.keychain.revoke(origin);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
username() {
|
|
220
|
+
if (!this.keychain) {
|
|
221
|
+
console.error(new NotReadyToConnect().message);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return this.keychain.username();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fetchControllers(
|
|
229
|
+
contractAddresses: string[],
|
|
230
|
+
): Promise<Record<string, string>> {
|
|
231
|
+
if (!this.keychain) {
|
|
232
|
+
throw new NotReadyToConnect().message;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return this.keychain.fetchControllers(contractAddresses);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async delegateAccount() {
|
|
239
|
+
if (!this.keychain) {
|
|
240
|
+
console.error(new NotReadyToConnect().message);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return await this.keychain.delegateAccount();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private waitForKeychain({
|
|
248
|
+
timeout = 5000,
|
|
249
|
+
interval = 100,
|
|
250
|
+
}:
|
|
251
|
+
| {
|
|
252
|
+
timeout?: number;
|
|
253
|
+
interval?: number;
|
|
254
|
+
}
|
|
255
|
+
| undefined = {}) {
|
|
256
|
+
return new Promise<void>((resolve, reject) => {
|
|
257
|
+
const startTime = Date.now();
|
|
258
|
+
const id = setInterval(() => {
|
|
259
|
+
if (Date.now() - startTime > timeout) {
|
|
260
|
+
clearInterval(id);
|
|
261
|
+
reject(new Error("Timeout waiting for keychain"));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!this.keychain) return;
|
|
265
|
+
|
|
266
|
+
clearInterval(id);
|
|
267
|
+
resolve();
|
|
268
|
+
}, interval);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
package/src/device.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Account,
|
|
3
|
+
Abi,
|
|
4
|
+
Call,
|
|
5
|
+
EstimateFeeDetails,
|
|
6
|
+
Signature,
|
|
7
|
+
InvokeFunctionResponse,
|
|
8
|
+
EstimateFee,
|
|
9
|
+
DeclareContractPayload,
|
|
10
|
+
RpcProvider,
|
|
11
|
+
TypedData,
|
|
12
|
+
InvocationsDetails,
|
|
13
|
+
} from "starknet";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
ConnectError,
|
|
17
|
+
Keychain,
|
|
18
|
+
KeychainOptions,
|
|
19
|
+
Modal,
|
|
20
|
+
ResponseCodes,
|
|
21
|
+
} from "./types";
|
|
22
|
+
import { Signer } from "./signer";
|
|
23
|
+
import { AsyncMethodReturns } from "@cartridge/penpal";
|
|
24
|
+
|
|
25
|
+
class DeviceAccount extends Account {
|
|
26
|
+
address: string;
|
|
27
|
+
private keychain: AsyncMethodReturns<Keychain>;
|
|
28
|
+
private modal: Modal;
|
|
29
|
+
private options?: KeychainOptions;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
rpcUrl: string,
|
|
33
|
+
address: string,
|
|
34
|
+
keychain: AsyncMethodReturns<Keychain>,
|
|
35
|
+
options: KeychainOptions,
|
|
36
|
+
modal: Modal,
|
|
37
|
+
) {
|
|
38
|
+
super(
|
|
39
|
+
new RpcProvider({ nodeUrl: rpcUrl }),
|
|
40
|
+
address,
|
|
41
|
+
new Signer(keychain, modal),
|
|
42
|
+
);
|
|
43
|
+
this.address = address;
|
|
44
|
+
this.keychain = keychain;
|
|
45
|
+
this.options = options;
|
|
46
|
+
this.modal = modal;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Estimate Fee for a method on starknet
|
|
51
|
+
*
|
|
52
|
+
* @param calls the invocation object containing:
|
|
53
|
+
* - contractAddress - the address of the contract
|
|
54
|
+
* - entrypoint - the entrypoint of the contract
|
|
55
|
+
* - calldata - (defaults to []) the calldata
|
|
56
|
+
* - signature - (defaults to []) the signature
|
|
57
|
+
*
|
|
58
|
+
* @returns response from addTransaction
|
|
59
|
+
*/
|
|
60
|
+
async estimateInvokeFee(
|
|
61
|
+
calls: Call | Call[],
|
|
62
|
+
details?: EstimateFeeDetails,
|
|
63
|
+
): Promise<EstimateFee> {
|
|
64
|
+
return this.keychain.estimateInvokeFee(calls, {
|
|
65
|
+
...details,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async estimateDeclareFee(
|
|
70
|
+
payload: DeclareContractPayload,
|
|
71
|
+
details?: EstimateFeeDetails,
|
|
72
|
+
): Promise<EstimateFee> {
|
|
73
|
+
return this.keychain.estimateDeclareFee(payload, {
|
|
74
|
+
...details,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Invoke execute function in account contract
|
|
80
|
+
*
|
|
81
|
+
* @param calls the invocation object or an array of them, containing:
|
|
82
|
+
* - contractAddress - the address of the contract
|
|
83
|
+
* - entrypoint - the entrypoint of the contract
|
|
84
|
+
* - calldata - (defaults to []) the calldata
|
|
85
|
+
* - signature - (defaults to []) the signature
|
|
86
|
+
* @param abis (optional) the abi of the contract for better displaying
|
|
87
|
+
*
|
|
88
|
+
* @returns response from addTransaction
|
|
89
|
+
*/
|
|
90
|
+
// @ts-expect-error TODO: fix overload type mismatch
|
|
91
|
+
async execute(
|
|
92
|
+
calls: Call | Call[],
|
|
93
|
+
abis?: Abi[],
|
|
94
|
+
transactionsDetail: InvocationsDetails = {},
|
|
95
|
+
): Promise<InvokeFunctionResponse> {
|
|
96
|
+
return new Promise(async (resolve, reject) => {
|
|
97
|
+
const sessionExecute = await this.keychain.execute(
|
|
98
|
+
calls,
|
|
99
|
+
abis,
|
|
100
|
+
transactionsDetail,
|
|
101
|
+
false,
|
|
102
|
+
this.options?.paymaster,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Session call succeeded
|
|
106
|
+
if (sessionExecute.code === ResponseCodes.SUCCESS) {
|
|
107
|
+
resolve(sessionExecute as InvokeFunctionResponse);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Propagates session txn error back to caller
|
|
112
|
+
if (this.options?.propagateSessionErrors) {
|
|
113
|
+
reject((sessionExecute as ConnectError).error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Session call or Paymaster flow failed.
|
|
118
|
+
// Session not avaialble, manual flow fallback
|
|
119
|
+
this.modal.open();
|
|
120
|
+
const manualExecute = await this.keychain.execute(
|
|
121
|
+
calls,
|
|
122
|
+
abis,
|
|
123
|
+
transactionsDetail,
|
|
124
|
+
true,
|
|
125
|
+
this.options?.paymaster,
|
|
126
|
+
(sessionExecute as ConnectError).error,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Manual call succeeded
|
|
130
|
+
if (manualExecute.code === ResponseCodes.SUCCESS) {
|
|
131
|
+
resolve(manualExecute as InvokeFunctionResponse);
|
|
132
|
+
this.modal.close();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
reject((manualExecute as ConnectError).error);
|
|
137
|
+
return;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Sign an JSON object for off-chain usage with the starknet private key and return the signature
|
|
143
|
+
* This adds a message prefix so it cant be interchanged with transactions
|
|
144
|
+
*
|
|
145
|
+
* @param json - JSON object to be signed
|
|
146
|
+
* @returns the signature of the JSON object
|
|
147
|
+
* @throws {Error} if the JSON object is not a valid JSON
|
|
148
|
+
*/
|
|
149
|
+
async signMessage(typedData: TypedData): Promise<Signature> {
|
|
150
|
+
try {
|
|
151
|
+
this.modal.open();
|
|
152
|
+
const res = await this.keychain.signMessage(typedData, this.address);
|
|
153
|
+
this.modal.close();
|
|
154
|
+
return res as Signature;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.error(e);
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default DeviceAccount;
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class NotReadyToConnect extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super("Not ready to connect");
|
|
4
|
+
|
|
5
|
+
Object.setPrototypeOf(this, NotReadyToConnect.prototype);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ProfileNotReady extends Error {
|
|
10
|
+
constructor() {
|
|
11
|
+
super("Profile is not ready");
|
|
12
|
+
|
|
13
|
+
Object.setPrototypeOf(this, NotReadyToConnect.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { AsyncMethodReturns, connectToChild } from "@cartridge/penpal";
|
|
2
|
+
import { defaultPresets } from "../presets";
|
|
3
|
+
import { ControllerOptions, Modal } from "../types";
|
|
4
|
+
|
|
5
|
+
export type IFrameOptions<CallSender> = Omit<
|
|
6
|
+
ConstructorParameters<typeof IFrame>[0],
|
|
7
|
+
"id" | "url" | "onConnect"
|
|
8
|
+
> & {
|
|
9
|
+
url?: string;
|
|
10
|
+
onConnect: (child: AsyncMethodReturns<CallSender>) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class IFrame<CallSender extends {}> implements Modal {
|
|
14
|
+
private iframe?: HTMLIFrameElement;
|
|
15
|
+
private container?: HTMLDivElement;
|
|
16
|
+
private onClose?: () => void;
|
|
17
|
+
|
|
18
|
+
constructor({
|
|
19
|
+
id,
|
|
20
|
+
url,
|
|
21
|
+
theme,
|
|
22
|
+
config,
|
|
23
|
+
colorMode,
|
|
24
|
+
onClose,
|
|
25
|
+
onConnect,
|
|
26
|
+
methods = {},
|
|
27
|
+
}: Pick<ControllerOptions, "theme" | "config" | "colorMode"> & {
|
|
28
|
+
id: string;
|
|
29
|
+
url: URL;
|
|
30
|
+
onClose?: () => void;
|
|
31
|
+
onConnect: (child: AsyncMethodReturns<CallSender>) => void;
|
|
32
|
+
methods?: { [key: string]: (...args: any[]) => void };
|
|
33
|
+
}) {
|
|
34
|
+
if (typeof document === "undefined") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
url.searchParams.set(
|
|
39
|
+
"theme",
|
|
40
|
+
encodeURIComponent(
|
|
41
|
+
JSON.stringify(
|
|
42
|
+
config?.presets?.[theme ?? "cartridge"] ?? defaultPresets.cartridge,
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (colorMode) {
|
|
48
|
+
url.searchParams.set("colorMode", colorMode);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const iframe = document.createElement("iframe");
|
|
52
|
+
iframe.src = url.toString();
|
|
53
|
+
iframe.id = id;
|
|
54
|
+
iframe.style.border = "none";
|
|
55
|
+
iframe.sandbox.add("allow-forms");
|
|
56
|
+
iframe.sandbox.add("allow-popups");
|
|
57
|
+
iframe.sandbox.add("allow-scripts");
|
|
58
|
+
iframe.sandbox.add("allow-same-origin");
|
|
59
|
+
iframe.allow =
|
|
60
|
+
"publickey-credentials-create *; publickey-credentials-get *; clipboard-write";
|
|
61
|
+
if (!!document.hasStorageAccess) {
|
|
62
|
+
iframe.sandbox.add("allow-storage-access-by-user-activation");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const container = document.createElement("div");
|
|
66
|
+
container.style.position = "fixed";
|
|
67
|
+
container.style.height = "100%";
|
|
68
|
+
container.style.width = "100%";
|
|
69
|
+
container.style.top = "0";
|
|
70
|
+
container.style.left = "0";
|
|
71
|
+
container.style.zIndex = "10000";
|
|
72
|
+
container.style.backgroundColor = "rgba(0,0,0,0.6)";
|
|
73
|
+
container.style.display = "flex";
|
|
74
|
+
container.style.alignItems = "center";
|
|
75
|
+
container.style.justifyContent = "center";
|
|
76
|
+
container.style.visibility = "hidden";
|
|
77
|
+
container.style.opacity = "0";
|
|
78
|
+
container.style.transition = "opacity 0.2s ease";
|
|
79
|
+
container.appendChild(iframe);
|
|
80
|
+
|
|
81
|
+
this.iframe = iframe;
|
|
82
|
+
this.container = container;
|
|
83
|
+
|
|
84
|
+
connectToChild<CallSender>({
|
|
85
|
+
iframe: this.iframe,
|
|
86
|
+
methods: { close: () => this.close(), ...methods },
|
|
87
|
+
}).promise.then(onConnect);
|
|
88
|
+
|
|
89
|
+
this.resize();
|
|
90
|
+
window.addEventListener("resize", () => this.resize());
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
document.readyState === "complete" ||
|
|
94
|
+
document.readyState === "interactive"
|
|
95
|
+
) {
|
|
96
|
+
this.append();
|
|
97
|
+
} else {
|
|
98
|
+
document.addEventListener("DOMContentLoaded", this.append);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.onClose = onClose;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
open() {
|
|
105
|
+
if (!this.container) return;
|
|
106
|
+
document.body.style.overflow = "hidden";
|
|
107
|
+
|
|
108
|
+
this.container.style.visibility = "visible";
|
|
109
|
+
this.container.style.opacity = "1";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
close() {
|
|
113
|
+
if (!this.container) return;
|
|
114
|
+
this.onClose?.();
|
|
115
|
+
|
|
116
|
+
document.body.style.overflow = "auto";
|
|
117
|
+
|
|
118
|
+
this.container.style.visibility = "hidden";
|
|
119
|
+
this.container.style.opacity = "0";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private append() {
|
|
123
|
+
if (!this.container) return;
|
|
124
|
+
document.body.appendChild(this.container);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private resize() {
|
|
128
|
+
if (!this.iframe) return;
|
|
129
|
+
if (window.innerWidth < 768) {
|
|
130
|
+
this.iframe.style.height = "100%";
|
|
131
|
+
this.iframe.style.width = "100%";
|
|
132
|
+
this.iframe.style.borderRadius = "0";
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.iframe.style.height = "600px";
|
|
137
|
+
this.iframe.style.width = "432px";
|
|
138
|
+
this.iframe.style.borderRadius = "8px";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KEYCHAIN_URL } from "../constants";
|
|
2
|
+
import { Keychain, KeychainOptions } from "../types";
|
|
3
|
+
import { IFrame, IFrameOptions } from "./base";
|
|
4
|
+
|
|
5
|
+
type KeychainIframeOptions = IFrameOptions<Keychain> & KeychainOptions;
|
|
6
|
+
|
|
7
|
+
export class KeychainIFrame extends IFrame<Keychain> {
|
|
8
|
+
constructor({
|
|
9
|
+
url,
|
|
10
|
+
paymaster,
|
|
11
|
+
policies,
|
|
12
|
+
...iframeOptions
|
|
13
|
+
}: KeychainIframeOptions) {
|
|
14
|
+
const _url = new URL(url ?? KEYCHAIN_URL);
|
|
15
|
+
if (paymaster) {
|
|
16
|
+
_url.searchParams.set(
|
|
17
|
+
"paymaster",
|
|
18
|
+
encodeURIComponent(JSON.stringify(paymaster)),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
if (policies) {
|
|
22
|
+
_url.searchParams.set(
|
|
23
|
+
"policies",
|
|
24
|
+
encodeURIComponent(JSON.stringify(policies)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
super({
|
|
29
|
+
...iframeOptions,
|
|
30
|
+
id: "controller-keychain",
|
|
31
|
+
url: _url,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PROFILE_URL } from "../constants";
|
|
2
|
+
import { Profile, ProfileOptions } from "../types";
|
|
3
|
+
import { IFrame, IFrameOptions } from "./base";
|
|
4
|
+
|
|
5
|
+
export type ProfileIFrameOptions = IFrameOptions<Profile> &
|
|
6
|
+
ProfileOptions & {
|
|
7
|
+
address: string;
|
|
8
|
+
username: string;
|
|
9
|
+
indexerUrl: string;
|
|
10
|
+
rpcUrl: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class ProfileIFrame extends IFrame<Profile> {
|
|
14
|
+
constructor({
|
|
15
|
+
profileUrl,
|
|
16
|
+
address,
|
|
17
|
+
username,
|
|
18
|
+
indexerUrl,
|
|
19
|
+
rpcUrl,
|
|
20
|
+
tokens,
|
|
21
|
+
...iframeOptions
|
|
22
|
+
}: ProfileIFrameOptions) {
|
|
23
|
+
const _url = new URL(profileUrl ?? PROFILE_URL);
|
|
24
|
+
_url.searchParams.set("address", encodeURIComponent(address));
|
|
25
|
+
_url.searchParams.set("username", encodeURIComponent(username));
|
|
26
|
+
_url.searchParams.set("indexerUrl", encodeURIComponent(indexerUrl));
|
|
27
|
+
_url.searchParams.set("rpcUrl", encodeURIComponent(rpcUrl));
|
|
28
|
+
|
|
29
|
+
if (tokens?.erc20) {
|
|
30
|
+
_url.searchParams.set(
|
|
31
|
+
"erc20",
|
|
32
|
+
encodeURIComponent(JSON.stringify(tokens.erc20)),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
super({
|
|
37
|
+
...iframeOptions,
|
|
38
|
+
id: "controller-profile",
|
|
39
|
+
url: _url,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|