@cartridge/controller 0.11.2 → 0.11.3

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.
Files changed (59) hide show
  1. package/.turbo/turbo-build$colon$deps.log +18 -18
  2. package/.turbo/turbo-build.log +16 -16
  3. package/dist/iframe/base.d.ts +0 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1854 -811
  6. package/dist/index.js.map +1 -1
  7. package/dist/node/index.cjs +1 -1
  8. package/dist/node/index.cjs.map +1 -1
  9. package/dist/node/index.d.cts +1 -1
  10. package/dist/node/index.d.ts +1 -1
  11. package/dist/node/index.js +1 -1
  12. package/dist/node/index.js.map +1 -1
  13. package/dist/{provider-s-80NdXp.js → provider-DCZ6z1Gs.js} +38 -24
  14. package/dist/provider-DCZ6z1Gs.js.map +1 -0
  15. package/dist/session.js +2 -2
  16. package/dist/stats.html +1 -1
  17. package/dist/toast/components/close-button.d.ts +1 -0
  18. package/dist/toast/components/progress-bar.d.ts +6 -0
  19. package/dist/toast/index.d.ts +57 -0
  20. package/dist/toast/styles.d.ts +1 -0
  21. package/dist/toast/types.d.ts +39 -0
  22. package/dist/toast/utils/progress-bar.d.ts +9 -0
  23. package/dist/toast/utils.d.ts +8 -0
  24. package/dist/toast/variants/achievement.d.ts +3 -0
  25. package/dist/toast/variants/error.d.ts +3 -0
  26. package/dist/toast/variants/index.d.ts +6 -0
  27. package/dist/toast/variants/marketplace.d.ts +3 -0
  28. package/dist/toast/variants/network-switch.d.ts +3 -0
  29. package/dist/toast/variants/quest.d.ts +3 -0
  30. package/dist/toast/variants/transaction.d.ts +3 -0
  31. package/dist/types.d.ts +3 -3
  32. package/dist/utils.d.ts +1 -0
  33. package/dist/wallets/index.d.ts +1 -0
  34. package/dist/wallets/phantom-evm/index.d.ts +7 -0
  35. package/dist/wallets/types.d.ts +2 -2
  36. package/package.json +2 -3
  37. package/src/iframe/base.ts +2 -20
  38. package/src/index.ts +1 -0
  39. package/src/toast/components/close-button.ts +82 -0
  40. package/src/toast/components/progress-bar.ts +60 -0
  41. package/src/toast/index.ts +263 -0
  42. package/src/toast/styles.ts +142 -0
  43. package/src/toast/types.ts +67 -0
  44. package/src/toast/utils/progress-bar.ts +23 -0
  45. package/src/toast/utils.ts +65 -0
  46. package/src/toast/variants/achievement.ts +244 -0
  47. package/src/toast/variants/error.ts +107 -0
  48. package/src/toast/variants/index.ts +6 -0
  49. package/src/toast/variants/marketplace.ts +145 -0
  50. package/src/toast/variants/network-switch.ts +72 -0
  51. package/src/toast/variants/quest.ts +164 -0
  52. package/src/toast/variants/transaction.ts +256 -0
  53. package/src/types.ts +1 -0
  54. package/src/utils.ts +15 -0
  55. package/src/wallets/bridge.ts +4 -0
  56. package/src/wallets/index.ts +1 -0
  57. package/src/wallets/phantom-evm/index.ts +8 -0
  58. package/src/wallets/types.ts +5 -1
  59. package/dist/provider-s-80NdXp.js.map +0 -1
@@ -0,0 +1,263 @@
1
+ import { ToastOptions } from "./types";
2
+ import {
3
+ isInIframe,
4
+ getTargetDocument,
5
+ getToastContainer,
6
+ DEFAULT_DURATION,
7
+ DEFAULT_POSITION,
8
+ TOAST_MESSAGE_TYPE,
9
+ removeToast,
10
+ } from "./utils";
11
+ import { injectStyles } from "./styles";
12
+ import {
13
+ injectErrorStyles,
14
+ createErrorToast,
15
+ injectTransactionStyles,
16
+ createTransactionToast,
17
+ injectNetworkSwitchStyles,
18
+ createNetworkSwitchToast,
19
+ injectAchievementStyles,
20
+ createAchievementToast,
21
+ injectQuestStyles,
22
+ createQuestToast,
23
+ injectMarketplaceStyles,
24
+ createMarketplaceToast,
25
+ } from "./variants";
26
+ import { addProgressBarToToast } from "./utils/progress-bar";
27
+
28
+ // Create toast element based on variant
29
+ function createToastElement(options: ToastOptions): HTMLElement {
30
+ switch (options.variant) {
31
+ case "error":
32
+ return createErrorToast(options);
33
+ case "transaction":
34
+ return createTransactionToast(options);
35
+ case "network-switch":
36
+ return createNetworkSwitchToast(options);
37
+ case "achievement":
38
+ return createAchievementToast(options);
39
+ case "quest":
40
+ return createQuestToast(options);
41
+ case "marketplace":
42
+ return createMarketplaceToast(options);
43
+ }
44
+ }
45
+
46
+ // Inject variant-specific styles
47
+ function injectVariantStyles(
48
+ targetDoc: Document,
49
+ variant: ToastOptions["variant"],
50
+ ): void {
51
+ switch (variant) {
52
+ case "error":
53
+ injectErrorStyles(targetDoc);
54
+ break;
55
+ case "transaction":
56
+ injectTransactionStyles(targetDoc);
57
+ break;
58
+ case "network-switch":
59
+ injectNetworkSwitchStyles(targetDoc);
60
+ break;
61
+ case "achievement":
62
+ injectAchievementStyles(targetDoc);
63
+ break;
64
+ case "quest":
65
+ injectQuestStyles(targetDoc);
66
+ break;
67
+ case "marketplace":
68
+ injectMarketplaceStyles(targetDoc);
69
+ break;
70
+ }
71
+ }
72
+
73
+ // Show toast on target document (parent or current)
74
+ function showToastOnDocument(
75
+ targetDoc: Document,
76
+ options: ToastOptions,
77
+ ): () => void {
78
+ // Inject common styles if needed
79
+ injectStyles(targetDoc);
80
+
81
+ // Inject variant-specific styles
82
+ injectVariantStyles(targetDoc, options.variant);
83
+
84
+ // Get container
85
+ const position = options.position || DEFAULT_POSITION;
86
+ const container = getToastContainer(targetDoc, position);
87
+
88
+ // Create toast element
89
+ const toastElement = createToastElement(options);
90
+
91
+ // Set up dismiss function
92
+ const dismiss = () => removeToast(toastElement);
93
+
94
+ // Add to container
95
+ container.appendChild(toastElement);
96
+
97
+ // Setup close button
98
+ const closeButton = toastElement.querySelector("#close-button");
99
+ if (closeButton) {
100
+ closeButton.addEventListener("click", dismiss);
101
+ }
102
+
103
+ // Handle duration and progress bar
104
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
105
+ const duration = options.duration ?? DEFAULT_DURATION;
106
+ const isInfiniteDuration = !isFinite(duration) || duration <= 0;
107
+
108
+ // Add progress bar to all variants except network-switch
109
+ if (options.variant !== "network-switch") {
110
+ // Determine border radius based on variant
111
+ const borderRadius =
112
+ options.variant === "error" || options.variant === "transaction" ? 8 : 4;
113
+
114
+ if (isInfiniteDuration) {
115
+ // Show static progress bar for infinite duration (no animation, no auto-dismiss)
116
+ addProgressBarToToast(toastElement, Infinity, () => {}, borderRadius);
117
+ } else {
118
+ // Animated progress bar with auto-dismiss
119
+ addProgressBarToToast(toastElement, duration, dismiss, borderRadius);
120
+ }
121
+ } else if (!isInfiniteDuration) {
122
+ // Network-switch variant uses setTimeout instead of progress bar
123
+ timeoutId = setTimeout(dismiss, duration);
124
+ }
125
+
126
+ // Return dismiss function
127
+ return () => {
128
+ if (timeoutId) {
129
+ clearTimeout(timeoutId);
130
+ }
131
+ dismiss();
132
+ };
133
+ }
134
+
135
+ // Set up message listener on parent window to handle toast requests from iframes
136
+ let messageListenerSetup = false;
137
+ function setupMessageListener(): void {
138
+ if (messageListenerSetup || typeof window === "undefined") {
139
+ return;
140
+ }
141
+
142
+ // Only set up listener on parent window (not in iframe)
143
+ if (isInIframe()) {
144
+ return;
145
+ }
146
+
147
+ window.addEventListener("message", (event) => {
148
+ // Basic origin check - in production, you might want stricter checks
149
+ if (event.data?.type === TOAST_MESSAGE_TYPE && event.data?.options) {
150
+ const targetDoc = document;
151
+ if (targetDoc) {
152
+ showToastOnDocument(targetDoc, event.data.options);
153
+ }
154
+ }
155
+ });
156
+
157
+ messageListenerSetup = true;
158
+ }
159
+
160
+ /**
161
+ * Show a toast notification
162
+ *
163
+ * The toast will always appear on the parent page, even if called from within an iframe.
164
+ * This ensures toasts are visible above all content, including the keychain iframe.
165
+ *
166
+ * @param options - Toast options with variant-specific properties
167
+ * @returns A function to manually dismiss the toast
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * import { toast } from "@cartridge/controller";
172
+ *
173
+ * // Error toast
174
+ * toast({
175
+ * variant: "error",
176
+ * message: "Transaction failed",
177
+ * });
178
+ *
179
+ * // Transaction toast
180
+ * toast({
181
+ * variant: "transaction",
182
+ * hash: "0x1234...",
183
+ * status: "success",
184
+ * amount: "100",
185
+ * token: "ETH"
186
+ * });
187
+ *
188
+ * // Network switch toast
189
+ * toast({
190
+ * variant: "network-switch",
191
+ * networkName: "Mainnet",
192
+ * networkIcon: <url to icon image>
193
+ * });
194
+ *
195
+ * // Achievement toast
196
+ * toast({
197
+ * variant: "achievement",
198
+ * itemName: "First Achievement",
199
+ * itemImage: "https://example.com/trophy.png"
200
+ * action: "purchased" | "sold",
201
+ * });
202
+ *
203
+ * // Marketplace toast
204
+ * toast({
205
+ * variant: "marketplace",
206
+ * itemName: "Cool NFT #123",
207
+ * action: "purchased",
208
+ * price: "0.5",
209
+ * currency: "ETH",
210
+ * image: "https://example.com/nft.png"
211
+ * });
212
+ * ```
213
+ */
214
+ export function toast(options: ToastOptions): () => void {
215
+ // Ensure we're in a browser environment
216
+ if (typeof window === "undefined" || typeof document === "undefined") {
217
+ console.warn("Toast can only be used in a browser environment");
218
+ return () => {};
219
+ }
220
+
221
+ // Set up message listener on parent window if not already set up
222
+ setupMessageListener();
223
+
224
+ // Check if we're in an iframe
225
+ if (isInIframe()) {
226
+ // Try to get parent document
227
+ const targetDoc = getTargetDocument();
228
+
229
+ if (targetDoc) {
230
+ // Same-origin iframe, can access parent document directly
231
+ return showToastOnDocument(targetDoc, options);
232
+ } else {
233
+ // Cross-origin iframe, use postMessage
234
+ try {
235
+ if (window.parent) {
236
+ window.parent.postMessage(
237
+ {
238
+ type: TOAST_MESSAGE_TYPE,
239
+ options: options,
240
+ },
241
+ "*", // In production, specify target origin
242
+ );
243
+ }
244
+ } catch (e) {
245
+ console.warn("Failed to send toast message to parent:", e);
246
+ }
247
+
248
+ // Return a no-op dismiss function for cross-origin case
249
+ return () => {};
250
+ }
251
+ } else {
252
+ // Not in iframe, show toast directly on current document
253
+ const targetDoc = document;
254
+ return showToastOnDocument(targetDoc, options);
255
+ }
256
+ }
257
+
258
+ export * from "./types";
259
+
260
+ // Initialize message listener when module loads (if in browser environment)
261
+ if (typeof window !== "undefined") {
262
+ setupMessageListener();
263
+ }
@@ -0,0 +1,142 @@
1
+ import { TOAST_CONTAINER_ID } from "./utils";
2
+
3
+ // Inject CSS styles if not already present
4
+ export function injectStyles(targetDoc: Document): void {
5
+ if (targetDoc.getElementById("cartridge-toast-styles")) {
6
+ return;
7
+ }
8
+
9
+ const style = targetDoc.createElement("style");
10
+ style.id = "cartridge-toast-styles";
11
+ style.textContent = getCommonStyles();
12
+ targetDoc.head.appendChild(style);
13
+ }
14
+
15
+ function getCommonStyles(): string {
16
+ return `
17
+ #${TOAST_CONTAINER_ID} {
18
+ position: fixed;
19
+ z-index: 999999;
20
+ pointer-events: none;
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: flex-end;
24
+ gap: 12px;
25
+ }
26
+
27
+ #${TOAST_CONTAINER_ID}.top-left {
28
+ top: 20px;
29
+ left: 20px;
30
+ align-items: flex-start;
31
+ }
32
+
33
+ #${TOAST_CONTAINER_ID}.top-right {
34
+ top: 20px;
35
+ right: 20px;
36
+ align-items: flex-end;
37
+ }
38
+
39
+ #${TOAST_CONTAINER_ID}.top-center {
40
+ top: 20px;
41
+ left: 50%;
42
+ transform: translateX(-50%);
43
+ align-items: center;
44
+ }
45
+
46
+ #${TOAST_CONTAINER_ID}.bottom-left {
47
+ bottom: 20px;
48
+ left: 20px;
49
+ align-items: flex-start;
50
+ }
51
+
52
+ #${TOAST_CONTAINER_ID}.bottom-right {
53
+ bottom: 20px;
54
+ right: 20px;
55
+ align-items: flex-end;
56
+ }
57
+
58
+ #${TOAST_CONTAINER_ID}.bottom-center {
59
+ bottom: 20px;
60
+ left: 50%;
61
+ transform: translateX(-50%);
62
+ align-items: center;
63
+ }
64
+
65
+ .cartridge-toast {
66
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
67
+ display: flex;
68
+ align-items: center;
69
+ animation: cartridge-toast-slide-in 0.3s ease-out;
70
+ overflow: hidden;
71
+ pointer-events: auto;
72
+ }
73
+
74
+ #${TOAST_CONTAINER_ID}.top-right .cartridge-toast,
75
+ #${TOAST_CONTAINER_ID}.bottom-right .cartridge-toast {
76
+ align-self: flex-end;
77
+ }
78
+
79
+ #${TOAST_CONTAINER_ID}.top-left .cartridge-toast,
80
+ #${TOAST_CONTAINER_ID}.bottom-left .cartridge-toast {
81
+ align-self: flex-start;
82
+ }
83
+
84
+ #${TOAST_CONTAINER_ID}.top-center .cartridge-toast,
85
+ #${TOAST_CONTAINER_ID}.bottom-center .cartridge-toast {
86
+ align-self: center;
87
+ }
88
+
89
+ @keyframes cartridge-toast-slide-in {
90
+ from {
91
+ opacity: 0;
92
+ transform: translateY(10px);
93
+ }
94
+ to {
95
+ opacity: 1;
96
+ transform: translateY(0);
97
+ }
98
+ }
99
+
100
+ .cartridge-toast.closing {
101
+ animation: cartridge-toast-slide-out 0.2s ease-in forwards;
102
+ }
103
+
104
+ @keyframes cartridge-toast-slide-out {
105
+ from {
106
+ opacity: 1;
107
+ transform: translateY(0);
108
+ }
109
+ to {
110
+ opacity: 0;
111
+ transform: translateY(10px);
112
+ }
113
+ }
114
+
115
+ @media (max-width: 640px) {
116
+ .cartridge-toast {
117
+ min-width: calc(100vw - 40px);
118
+ max-width: calc(100vw - 40px);
119
+ }
120
+
121
+ #${TOAST_CONTAINER_ID}.top-left,
122
+ #${TOAST_CONTAINER_ID}.top-right,
123
+ #${TOAST_CONTAINER_ID}.top-center {
124
+ top: 10px;
125
+ left: 20px;
126
+ right: 20px;
127
+ transform: none;
128
+ align-items: stretch;
129
+ }
130
+
131
+ #${TOAST_CONTAINER_ID}.bottom-left,
132
+ #${TOAST_CONTAINER_ID}.bottom-right,
133
+ #${TOAST_CONTAINER_ID}.bottom-center {
134
+ bottom: 10px;
135
+ left: 20px;
136
+ right: 20px;
137
+ transform: none;
138
+ align-items: stretch;
139
+ }
140
+ }
141
+ `;
142
+ }
@@ -0,0 +1,67 @@
1
+ export type ToastPosition =
2
+ | "top-left"
3
+ | "top-right"
4
+ | "top-center"
5
+ | "bottom-left"
6
+ | "bottom-right"
7
+ | "bottom-center";
8
+
9
+ // Base toast options shared by all variants
10
+ export interface BaseToastOptions {
11
+ duration?: number; // in milliseconds, 0 means persistent
12
+ position?: ToastPosition;
13
+ }
14
+
15
+ // Error Toast
16
+ export interface ErrorToastOptions extends BaseToastOptions {
17
+ variant: "error";
18
+ message: string;
19
+ }
20
+
21
+ // Transaction Toast
22
+ export interface TransactionToastOptions extends BaseToastOptions {
23
+ variant: "transaction";
24
+ status: "confirming" | "confirmed";
25
+ isExpanded?: boolean;
26
+ label?: string;
27
+ }
28
+
29
+ // Network Switch Toast
30
+ export interface NetworkSwitchToastOptions extends BaseToastOptions {
31
+ variant: "network-switch";
32
+ networkName: string;
33
+ networkIcon?: string;
34
+ }
35
+
36
+ // Achievement Toast
37
+ export interface AchievementToastOptions extends BaseToastOptions {
38
+ variant: "achievement";
39
+ title: string;
40
+ subtitle?: string;
41
+ xpAmount: number;
42
+ isDraft?: boolean;
43
+ }
44
+
45
+ // Quest Toast
46
+ export interface QuestToastOptions extends BaseToastOptions {
47
+ variant: "quest";
48
+ title: string;
49
+ subtitle: string;
50
+ }
51
+
52
+ // Marketplace Toast
53
+ export interface MarketplaceToastOptions extends BaseToastOptions {
54
+ variant: "marketplace";
55
+ itemName: string;
56
+ itemImage: string;
57
+ action: "purchased" | "sold";
58
+ }
59
+
60
+ // Union type for all toast variants
61
+ export type ToastOptions =
62
+ | ErrorToastOptions
63
+ | TransactionToastOptions
64
+ | NetworkSwitchToastOptions
65
+ | AchievementToastOptions
66
+ | QuestToastOptions
67
+ | MarketplaceToastOptions;
@@ -0,0 +1,23 @@
1
+ import { ProgressBar } from "../components/progress-bar";
2
+
3
+ /**
4
+ * Add a progress bar to a toast element
5
+ *
6
+ * @param toast - The toast element to add the progress bar to
7
+ * @param duration - Duration in milliseconds
8
+ * @param onComplete - Callback when progress completes
9
+ * @param borderRadius - Optional border radius in pixels (default: 8px)
10
+ */
11
+ export function addProgressBarToToast(
12
+ toast: HTMLElement,
13
+ duration: number,
14
+ onComplete: () => void,
15
+ borderRadius?: number,
16
+ ): void {
17
+ const progressBar = ProgressBar({
18
+ duration,
19
+ onComplete,
20
+ borderRadius,
21
+ });
22
+ toast.appendChild(progressBar);
23
+ }
@@ -0,0 +1,65 @@
1
+ export const TOAST_CONTAINER_ID = "cartridge-toast-container";
2
+ export const DEFAULT_DURATION = 3000;
3
+ export const DEFAULT_POSITION = "bottom-right";
4
+ export const TOAST_MESSAGE_TYPE = "cartridge-toast-show";
5
+
6
+ // Check if we're in an iframe
7
+ export function isInIframe(): boolean {
8
+ try {
9
+ return typeof window !== "undefined" && window.self !== window.top;
10
+ } catch {
11
+ return true; // If we can't access window.top, we're likely in an iframe
12
+ }
13
+ }
14
+
15
+ // Get the target document (parent if in iframe, current if not)
16
+ export function getTargetDocument(): Document | null {
17
+ if (typeof document === "undefined") {
18
+ return null;
19
+ }
20
+
21
+ if (isInIframe()) {
22
+ try {
23
+ // Try to access parent document
24
+ if (window.parent && window.parent.document) {
25
+ return window.parent.document;
26
+ }
27
+ } catch (e) {
28
+ console.warn("Failed to access parent document:", e);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ return document;
34
+ }
35
+
36
+ // Get or create toast container
37
+ export function getToastContainer(
38
+ targetDoc: Document,
39
+ position: string,
40
+ ): HTMLElement {
41
+ let container = targetDoc.getElementById(TOAST_CONTAINER_ID);
42
+
43
+ if (!container) {
44
+ container = targetDoc.createElement("div");
45
+ container.id = TOAST_CONTAINER_ID;
46
+ if (targetDoc.body) {
47
+ targetDoc.body.appendChild(container);
48
+ }
49
+ }
50
+
51
+ // Update position class
52
+ container.className = position;
53
+
54
+ return container;
55
+ }
56
+
57
+ // Remove toast with animation
58
+ export function removeToast(toast: HTMLElement): void {
59
+ toast.classList.add("closing");
60
+ setTimeout(() => {
61
+ if (toast.parentNode) {
62
+ toast.parentNode.removeChild(toast);
63
+ }
64
+ }, 200);
65
+ }