@blu1606/create-walrus-app 1.0.0 → 2.1.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/dist/generator/file-ops.d.ts +8 -0
- package/dist/generator/file-ops.js +20 -0
- package/dist/generator/index.js +37 -22
- package/dist/generator/layers.d.ts +15 -2
- package/dist/generator/layers.js +38 -47
- package/dist/generator/types.d.ts +9 -1
- package/dist/index.js +1 -2
- package/dist/post-install/git.d.ts +8 -0
- package/dist/post-install/git.js +2 -0
- package/dist/post-install/index.d.ts +0 -1
- package/dist/post-install/index.js +7 -15
- package/dist/post-install/messages.js +1 -1
- package/dist/post-install/walrus-deploy.d.ts +6 -0
- package/dist/post-install/walrus-deploy.js +77 -0
- package/package.json +3 -3
- package/{templates/base → presets/react-mysten-gallery}/.env.example +31 -31
- package/presets/react-mysten-gallery/.gitkeep +4 -0
- package/presets/react-mysten-gallery/README.md +107 -0
- package/presets/react-mysten-gallery/package.json +35 -0
- package/presets/react-mysten-gallery/scripts/setup-walrus-deploy.sh +286 -0
- package/presets/react-mysten-gallery/src/App.tsx +23 -0
- package/presets/react-mysten-gallery/src/components/features/file-card.tsx +89 -0
- package/{templates/gallery/src/components/GalleryGrid.tsx → presets/react-mysten-gallery/src/components/features/gallery-grid.tsx} +5 -5
- package/presets/react-mysten-gallery/src/components/features/upload-modal.tsx +69 -0
- package/{templates/react/src/components/WalletConnect.tsx → presets/react-mysten-gallery/src/components/features/wallet-connect.tsx} +1 -1
- package/presets/react-mysten-gallery/src/components/layout/app-layout.tsx +21 -0
- package/{templates/react/src/hooks/useStorage.ts → presets/react-mysten-gallery/src/hooks/use-download.ts} +2 -18
- package/presets/react-mysten-gallery/src/hooks/use-upload.ts +49 -0
- package/{templates/react/src/hooks/useWallet.ts → presets/react-mysten-gallery/src/hooks/use-wallet.ts} +2 -7
- package/presets/react-mysten-gallery/src/index.css +384 -0
- package/presets/react-mysten-gallery/src/index.ts +17 -0
- package/presets/react-mysten-gallery/src/lib/walrus/adapter.ts +197 -0
- package/presets/react-mysten-gallery/src/lib/walrus/client.ts +87 -0
- package/presets/react-mysten-gallery/src/lib/walrus/index.ts +4 -0
- package/presets/react-mysten-gallery/src/lib/walrus/types.ts +101 -0
- package/{templates/react → presets/react-mysten-gallery}/src/main.tsx +0 -1
- package/{templates/react → presets/react-mysten-gallery}/src/providers/WalletProvider.tsx +16 -1
- package/{templates/base → presets/react-mysten-gallery}/src/utils/env.ts +41 -41
- package/{templates/gallery → presets/react-mysten-gallery}/src/utils/index-manager.ts +2 -2
- package/presets/react-mysten-gallery/src/utils/mime-type.ts +97 -0
- package/presets/react-mysten-gallery/src/utils/preview-generator.ts +134 -0
- package/{templates/react → presets/react-mysten-gallery}/tsconfig.json +20 -8
- package/presets/react-mysten-simple-upload/.env.example +31 -0
- package/presets/react-mysten-simple-upload/.gitkeep +4 -0
- package/presets/react-mysten-simple-upload/README.md +84 -0
- package/presets/react-mysten-simple-upload/index.html +13 -0
- package/{templates/react → presets/react-mysten-simple-upload}/package.json +15 -12
- package/presets/react-mysten-simple-upload/scripts/setup-walrus-deploy.sh +286 -0
- package/presets/react-mysten-simple-upload/src/App.tsx +27 -0
- package/presets/react-mysten-simple-upload/src/components/features/file-preview.tsx +73 -0
- package/{templates/simple-upload/src/components/UploadForm.tsx → presets/react-mysten-simple-upload/src/components/features/upload-form.tsx} +15 -5
- package/presets/react-mysten-simple-upload/src/components/features/wallet-connect.tsx +21 -0
- package/presets/react-mysten-simple-upload/src/components/layout/app-layout.tsx +21 -0
- package/presets/react-mysten-simple-upload/src/hooks/use-download.ts +24 -0
- package/presets/react-mysten-simple-upload/src/hooks/use-upload.ts +49 -0
- package/presets/react-mysten-simple-upload/src/hooks/use-wallet.ts +11 -0
- package/presets/react-mysten-simple-upload/src/index.css +252 -0
- package/presets/react-mysten-simple-upload/src/index.ts +16 -0
- package/presets/react-mysten-simple-upload/src/lib/walrus/adapter.ts +197 -0
- package/presets/react-mysten-simple-upload/src/lib/walrus/client.ts +87 -0
- package/presets/react-mysten-simple-upload/src/lib/walrus/index.ts +4 -0
- package/{templates/base/src/adapters/storage.ts → presets/react-mysten-simple-upload/src/lib/walrus/types.ts} +83 -58
- package/presets/react-mysten-simple-upload/src/main.tsx +16 -0
- package/presets/react-mysten-simple-upload/src/providers/QueryProvider.tsx +18 -0
- package/presets/react-mysten-simple-upload/src/providers/WalletProvider.tsx +52 -0
- package/presets/react-mysten-simple-upload/src/utils/env.ts +41 -0
- package/presets/react-mysten-simple-upload/src/utils/mime-type.ts +97 -0
- package/presets/react-mysten-simple-upload/tsconfig.json +39 -0
- package/presets/react-mysten-simple-upload/tsconfig.node.json +10 -0
- package/presets/react-mysten-simple-upload/vite.config.ts +19 -0
- package/templates/base/README.md +0 -54
- package/templates/base/package.json +0 -19
- package/templates/base/src/types/index.ts +0 -9
- package/templates/base/src/types/walrus.ts +0 -22
- package/templates/base/src/utils/format.ts +0 -29
- package/templates/base/tsconfig.json +0 -19
- package/templates/gallery/README.md +0 -44
- package/templates/gallery/package.json +0 -6
- package/templates/gallery/src/App.tsx +0 -21
- package/templates/gallery/src/components/FileCard.tsx +0 -27
- package/templates/gallery/src/components/UploadModal.tsx +0 -45
- package/templates/gallery/src/styles.css +0 -58
- package/templates/gallery/src/types/gallery.ts +0 -13
- package/templates/react/.eslintrc.json +0 -26
- package/templates/react/README.md +0 -80
- package/templates/react/src/App.tsx +0 -14
- package/templates/react/src/components/Layout.tsx +0 -21
- package/templates/react/src/dapp-kit.css +0 -1
- package/templates/react/src/index.css +0 -50
- package/templates/react/src/index.ts +0 -10
- package/templates/sdk-mysten/README.md +0 -65
- package/templates/sdk-mysten/package.json +0 -14
- package/templates/sdk-mysten/src/adapter.ts +0 -80
- package/templates/sdk-mysten/src/client.ts +0 -45
- package/templates/sdk-mysten/src/config.ts +0 -33
- package/templates/sdk-mysten/src/index.ts +0 -11
- package/templates/sdk-mysten/src/types.ts +0 -19
- package/templates/sdk-mysten/test/adapter.test.ts +0 -20
- package/templates/simple-upload/README.md +0 -24
- package/templates/simple-upload/package.json +0 -6
- package/templates/simple-upload/src/App.tsx +0 -27
- package/templates/simple-upload/src/components/FilePreview.tsx +0 -40
- package/templates/simple-upload/src/styles.css +0 -33
- /package/{templates/react → presets/react-mysten-gallery}/index.html +0 -0
- /package/{templates/react → presets/react-mysten-gallery}/src/providers/QueryProvider.tsx +0 -0
- /package/{templates/react → presets/react-mysten-gallery}/tsconfig.node.json +0 -0
- /package/{templates/react → presets/react-mysten-gallery}/vite.config.ts +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { walrus, WalrusClient } from '@mysten/walrus';
|
|
2
|
+
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
|
|
3
|
+
import { loadEnv } from '../../utils/env.js';
|
|
4
|
+
|
|
5
|
+
// Types
|
|
6
|
+
export type WalrusNetwork = 'testnet' | 'mainnet';
|
|
7
|
+
|
|
8
|
+
export interface MystenWalrusConfig {
|
|
9
|
+
network: WalrusNetwork;
|
|
10
|
+
publisherUrl?: string;
|
|
11
|
+
aggregatorUrl?: string;
|
|
12
|
+
suiRpcUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type WalrusExtendedClient = SuiClient & { walrus: WalrusClient };
|
|
16
|
+
|
|
17
|
+
// Network Configurations
|
|
18
|
+
const NETWORK_CONFIGS: Record<WalrusNetwork, MystenWalrusConfig> = {
|
|
19
|
+
testnet: {
|
|
20
|
+
network: 'testnet',
|
|
21
|
+
publisherUrl: 'https://publisher.walrus-testnet.walrus.space',
|
|
22
|
+
aggregatorUrl: 'https://aggregator.walrus-testnet.walrus.space',
|
|
23
|
+
suiRpcUrl: 'https://fullnode.testnet.sui.io:443',
|
|
24
|
+
},
|
|
25
|
+
mainnet: {
|
|
26
|
+
network: 'mainnet',
|
|
27
|
+
publisherUrl: 'https://publisher.walrus.space',
|
|
28
|
+
aggregatorUrl: 'https://aggregator.walrus.space',
|
|
29
|
+
suiRpcUrl: 'https://fullnode.mainnet.sui.io:443',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function getNetworkConfig(network: WalrusNetwork): MystenWalrusConfig {
|
|
34
|
+
return NETWORK_CONFIGS[network];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Client Management
|
|
38
|
+
let walrusClient: WalrusExtendedClient | null = null;
|
|
39
|
+
|
|
40
|
+
export function getWalrusClient(): WalrusExtendedClient {
|
|
41
|
+
if (walrusClient) {
|
|
42
|
+
return walrusClient;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const env = loadEnv();
|
|
46
|
+
|
|
47
|
+
// Validate network value before casting
|
|
48
|
+
const allowedNetworks: WalrusNetwork[] = ['testnet', 'mainnet'];
|
|
49
|
+
if (!allowedNetworks.includes(env.walrusNetwork as WalrusNetwork)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Invalid WALRUS_NETWORK: ${env.walrusNetwork}. Must be one of: ${allowedNetworks.join(', ')}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const network = env.walrusNetwork as WalrusNetwork;
|
|
55
|
+
const config = getNetworkConfig(network);
|
|
56
|
+
|
|
57
|
+
// Use SuiClient (required for Walrus SDK and CoinWithBalance intent)
|
|
58
|
+
const suiClient = new SuiClient({
|
|
59
|
+
url:
|
|
60
|
+
env.suiRpc ||
|
|
61
|
+
config.suiRpcUrl ||
|
|
62
|
+
getFullnodeUrl(network === 'testnet' ? 'testnet' : 'mainnet'),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Extend the client with Walrus capabilities and upload relay
|
|
66
|
+
walrusClient = suiClient.$extend(
|
|
67
|
+
walrus({
|
|
68
|
+
network: network, // REQUIRED: Walrus SDK needs to know which network
|
|
69
|
+
// Use CDN for WASM - more reliable than bundler resolution
|
|
70
|
+
wasmUrl: 'https://unpkg.com/@mysten/walrus-wasm@latest/web/walrus_wasm_bg.wasm',
|
|
71
|
+
uploadRelay: {
|
|
72
|
+
host:
|
|
73
|
+
env.walrusPublisher ||
|
|
74
|
+
config.publisherUrl ||
|
|
75
|
+
`https://upload-relay.${network}.walrus.space`,
|
|
76
|
+
sendTip: null, // Skip tip config fetch to avoid CORS issues in development
|
|
77
|
+
},
|
|
78
|
+
...(env.walrusAggregator && { aggregatorUrl: env.walrusAggregator }),
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return walrusClient;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function resetWalrusClient(): void {
|
|
86
|
+
walrusClient = null;
|
|
87
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal storage adapter interface for Walrus
|
|
3
|
+
*
|
|
4
|
+
* This interface abstracts SDK-specific implementations,
|
|
5
|
+
* allowing use case layers to work with any Walrus SDK.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SuiClient } from '@mysten/sui/client';
|
|
9
|
+
import type { Transaction } from '@mysten/sui/transactions';
|
|
10
|
+
|
|
11
|
+
export interface BlobMetadata {
|
|
12
|
+
blobId: string;
|
|
13
|
+
size: number;
|
|
14
|
+
contentType?: string;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
/** Original filename with extension */
|
|
18
|
+
fileName?: string;
|
|
19
|
+
/** All tags from WalrusFile */
|
|
20
|
+
tags?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SignAndExecuteTransactionArgs {
|
|
24
|
+
transaction: Transaction;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SignAndExecuteTransactionResult {
|
|
28
|
+
digest: string;
|
|
29
|
+
effects?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UploadOptions {
|
|
33
|
+
/** Number of epochs to store (Walrus-specific) */
|
|
34
|
+
epochs?: number;
|
|
35
|
+
/** MIME type of the content */
|
|
36
|
+
contentType?: string;
|
|
37
|
+
/** Sui client with Walrus extension (from useSuiClient hook) */
|
|
38
|
+
client?: SuiClient;
|
|
39
|
+
/** Wallet signer for authenticated uploads with address and transaction signing */
|
|
40
|
+
signer?: {
|
|
41
|
+
address: string;
|
|
42
|
+
signAndExecuteTransaction: (args: SignAndExecuteTransactionArgs) => Promise<SignAndExecuteTransactionResult>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DownloadOptions {
|
|
47
|
+
/** Byte range (for large files) */
|
|
48
|
+
range?: { start: number; end: number };
|
|
49
|
+
/** Output format - auto-detects if not specified */
|
|
50
|
+
format?: 'bytes' | 'text' | 'json';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface StorageAdapter {
|
|
54
|
+
/**
|
|
55
|
+
* Upload data to Walrus storage
|
|
56
|
+
* @param data - File or raw bytes to upload
|
|
57
|
+
* @param options - Upload configuration
|
|
58
|
+
* @returns Blob ID (permanent reference)
|
|
59
|
+
*/
|
|
60
|
+
upload(data: File | Uint8Array, options?: UploadOptions): Promise<string>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Download blob data by ID
|
|
64
|
+
* @param blobId - Unique blob identifier
|
|
65
|
+
* @param options - Download configuration
|
|
66
|
+
* @returns Blob data in requested format (Uint8Array, string, or JSON)
|
|
67
|
+
*/
|
|
68
|
+
download(blobId: string, options?: DownloadOptions): Promise<Uint8Array | string | unknown>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get blob metadata without downloading content
|
|
72
|
+
* @param blobId - Unique blob identifier
|
|
73
|
+
* @returns Metadata object
|
|
74
|
+
*/
|
|
75
|
+
getMetadata(blobId: string): Promise<BlobMetadata>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if blob exists
|
|
79
|
+
* @param blobId - Unique blob identifier
|
|
80
|
+
* @returns True if blob is accessible
|
|
81
|
+
*/
|
|
82
|
+
exists(blobId: string): Promise<boolean>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Gallery-specific types
|
|
86
|
+
|
|
87
|
+
export interface GalleryItem {
|
|
88
|
+
blobId: string;
|
|
89
|
+
name: string;
|
|
90
|
+
size: number;
|
|
91
|
+
contentType: string;
|
|
92
|
+
uploadedAt: number;
|
|
93
|
+
/** Preview URL for displaying images/media - created from blob data */
|
|
94
|
+
previewUrl?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface GalleryIndex {
|
|
98
|
+
version: '1.0';
|
|
99
|
+
items: GalleryItem[];
|
|
100
|
+
lastModified: number;
|
|
101
|
+
}
|
|
@@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client';
|
|
|
3
3
|
import { QueryProvider } from './providers/QueryProvider.js';
|
|
4
4
|
import { WalletProvider } from './providers/WalletProvider.js';
|
|
5
5
|
import App from './App.js';
|
|
6
|
-
import './dapp-kit.css';
|
|
7
6
|
import './index.css';
|
|
8
7
|
|
|
9
8
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
SuiClientProvider,
|
|
4
4
|
WalletProvider as SuiWalletProvider,
|
|
5
5
|
} from '@mysten/dapp-kit';
|
|
6
|
-
import { getFullnodeUrl } from '@mysten/sui/client';
|
|
6
|
+
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
|
|
7
|
+
import { walrus } from '@mysten/walrus';
|
|
7
8
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
8
9
|
import { ReactNode } from 'react';
|
|
9
10
|
import { loadEnv } from '../utils/env.js';
|
|
@@ -29,6 +30,20 @@ export function WalletProvider({ children }: { children: ReactNode }) {
|
|
|
29
30
|
<SuiClientProvider
|
|
30
31
|
networks={networkConfig}
|
|
31
32
|
defaultNetwork={validatedNetwork}
|
|
33
|
+
createClient={(network: string | number, config: { readonly url: string }) => {
|
|
34
|
+
// Create SuiClient with Walrus extension
|
|
35
|
+
const client = new SuiClient({
|
|
36
|
+
url: config.url,
|
|
37
|
+
}).$extend(
|
|
38
|
+
walrus({
|
|
39
|
+
network: network as 'testnet' | 'mainnet',
|
|
40
|
+
wasmUrl: 'https://unpkg.com/@mysten/walrus-wasm@latest/web/walrus_wasm_bg.wasm',
|
|
41
|
+
// No uploadRelay - upload directly to storage nodes
|
|
42
|
+
// This avoids tip payment requirements but uses ~2200 requests
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
return client;
|
|
46
|
+
}}
|
|
32
47
|
>
|
|
33
48
|
<SuiWalletProvider>{children}</SuiWalletProvider>
|
|
34
49
|
</SuiClientProvider>
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
export interface EnvConfig {
|
|
2
|
-
walrusNetwork: string;
|
|
3
|
-
walrusAggregator: string;
|
|
4
|
-
walrusPublisher: string;
|
|
5
|
-
suiNetwork: string;
|
|
6
|
-
suiRpc: string;
|
|
7
|
-
blockberryKey?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function loadEnv(): EnvConfig {
|
|
11
|
-
const getEnv = (key: string, required = true): string => {
|
|
12
|
-
const value = import.meta.env[key];
|
|
13
|
-
if (required && !value) {
|
|
14
|
-
throw new Error(`Missing required environment variable: ${key}`);
|
|
15
|
-
}
|
|
16
|
-
return value || '';
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
walrusNetwork: getEnv('VITE_WALRUS_NETWORK'),
|
|
21
|
-
walrusAggregator: getEnv('VITE_WALRUS_AGGREGATOR'),
|
|
22
|
-
walrusPublisher: getEnv('VITE_WALRUS_PUBLISHER'),
|
|
23
|
-
suiNetwork: getEnv('VITE_SUI_NETWORK'),
|
|
24
|
-
suiRpc: getEnv('VITE_SUI_RPC'),
|
|
25
|
-
blockberryKey: getEnv('VITE_BLOCKBERRY_KEY', false),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function validateEnv(config: EnvConfig): void {
|
|
30
|
-
if (!['testnet', 'mainnet', 'devnet'].includes(config.walrusNetwork)) {
|
|
31
|
-
throw new Error(`Invalid WALRUS_NETWORK: ${config.walrusNetwork}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!config.walrusAggregator.startsWith('http')) {
|
|
35
|
-
throw new Error('WALRUS_AGGREGATOR must be a valid HTTP URL');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!config.walrusPublisher.startsWith('http')) {
|
|
39
|
-
throw new Error('WALRUS_PUBLISHER must be a valid HTTP URL');
|
|
40
|
-
}
|
|
41
|
-
}
|
|
1
|
+
export interface EnvConfig {
|
|
2
|
+
walrusNetwork: string;
|
|
3
|
+
walrusAggregator: string;
|
|
4
|
+
walrusPublisher: string;
|
|
5
|
+
suiNetwork: string;
|
|
6
|
+
suiRpc: string;
|
|
7
|
+
blockberryKey?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadEnv(): EnvConfig {
|
|
11
|
+
const getEnv = (key: string, required = true): string => {
|
|
12
|
+
const value = import.meta.env[key];
|
|
13
|
+
if (required && !value) {
|
|
14
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
15
|
+
}
|
|
16
|
+
return value || '';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
walrusNetwork: getEnv('VITE_WALRUS_NETWORK'),
|
|
21
|
+
walrusAggregator: getEnv('VITE_WALRUS_AGGREGATOR'),
|
|
22
|
+
walrusPublisher: getEnv('VITE_WALRUS_PUBLISHER'),
|
|
23
|
+
suiNetwork: getEnv('VITE_SUI_NETWORK'),
|
|
24
|
+
suiRpc: getEnv('VITE_SUI_RPC'),
|
|
25
|
+
blockberryKey: getEnv('VITE_BLOCKBERRY_KEY', false),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validateEnv(config: EnvConfig): void {
|
|
30
|
+
if (!['testnet', 'mainnet', 'devnet'].includes(config.walrusNetwork)) {
|
|
31
|
+
throw new Error(`Invalid WALRUS_NETWORK: ${config.walrusNetwork}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!config.walrusAggregator.startsWith('http')) {
|
|
35
|
+
throw new Error('WALRUS_AGGREGATOR must be a valid HTTP URL');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!config.walrusPublisher.startsWith('http')) {
|
|
39
|
+
throw new Error('WALRUS_PUBLISHER must be a valid HTTP URL');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GalleryIndex, GalleryItem } from '../types
|
|
1
|
+
import type { GalleryIndex, GalleryItem } from '../lib/walrus/types.js';
|
|
2
2
|
|
|
3
3
|
const INDEX_KEY = 'gallery-index';
|
|
4
4
|
|
|
@@ -34,4 +34,4 @@ export function removeItem(blobId: string): void {
|
|
|
34
34
|
const index = loadIndex();
|
|
35
35
|
index.items = index.items.filter((item) => item.blobId !== blobId);
|
|
36
36
|
saveIndex(index);
|
|
37
|
-
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME type detection from file extensions
|
|
3
|
+
* Handles common file types that browsers may not auto-detect
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const MIME_TYPES: Record<string, string> = {
|
|
7
|
+
// Text formats
|
|
8
|
+
'.txt': 'text/plain',
|
|
9
|
+
'.md': 'text/markdown',
|
|
10
|
+
'.markdown': 'text/markdown',
|
|
11
|
+
'.csv': 'text/csv',
|
|
12
|
+
'.html': 'text/html',
|
|
13
|
+
'.css': 'text/css',
|
|
14
|
+
'.js': 'text/javascript',
|
|
15
|
+
'.ts': 'text/typescript',
|
|
16
|
+
'.json': 'application/json',
|
|
17
|
+
'.xml': 'application/xml',
|
|
18
|
+
|
|
19
|
+
// Images
|
|
20
|
+
'.jpg': 'image/jpeg',
|
|
21
|
+
'.jpeg': 'image/jpeg',
|
|
22
|
+
'.png': 'image/png',
|
|
23
|
+
'.gif': 'image/gif',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.webp': 'image/webp',
|
|
26
|
+
'.ico': 'image/x-icon',
|
|
27
|
+
|
|
28
|
+
// Documents
|
|
29
|
+
'.pdf': 'application/pdf',
|
|
30
|
+
'.doc': 'application/msword',
|
|
31
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
32
|
+
'.xls': 'application/vnd.ms-excel',
|
|
33
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
34
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
35
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
36
|
+
|
|
37
|
+
// Archives
|
|
38
|
+
'.zip': 'application/zip',
|
|
39
|
+
'.rar': 'application/x-rar-compressed',
|
|
40
|
+
'.tar': 'application/x-tar',
|
|
41
|
+
'.gz': 'application/gzip',
|
|
42
|
+
'.7z': 'application/x-7z-compressed',
|
|
43
|
+
|
|
44
|
+
// Audio
|
|
45
|
+
'.mp3': 'audio/mpeg',
|
|
46
|
+
'.wav': 'audio/wav',
|
|
47
|
+
'.ogg': 'audio/ogg',
|
|
48
|
+
'.m4a': 'audio/mp4',
|
|
49
|
+
|
|
50
|
+
// Video
|
|
51
|
+
'.mp4': 'video/mp4',
|
|
52
|
+
'.webm': 'video/webm',
|
|
53
|
+
'.avi': 'video/x-msvideo',
|
|
54
|
+
'.mov': 'video/quicktime',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect MIME type from filename
|
|
59
|
+
* @param fileName - File name with extension
|
|
60
|
+
* @returns MIME type or null if not found
|
|
61
|
+
*/
|
|
62
|
+
export function getMimeTypeFromFileName(fileName: string): string | null {
|
|
63
|
+
const extension = fileName.toLowerCase().match(/\.[^.]+$/)?.[0];
|
|
64
|
+
if (!extension) return null;
|
|
65
|
+
|
|
66
|
+
return MIME_TYPES[extension] || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get content type with fallback logic
|
|
71
|
+
* Priority: explicit contentType > File.type > extension detection > fallback
|
|
72
|
+
* @param file - File object, Uint8Array, or filename
|
|
73
|
+
* @param explicitType - Explicitly provided content type
|
|
74
|
+
* @returns Best-match content type
|
|
75
|
+
*/
|
|
76
|
+
export function getContentType(
|
|
77
|
+
file: File | Uint8Array | string,
|
|
78
|
+
explicitType?: string
|
|
79
|
+
): string {
|
|
80
|
+
// Priority 1: Explicit content type
|
|
81
|
+
if (explicitType) return explicitType;
|
|
82
|
+
|
|
83
|
+
// Priority 2: File.type (if not empty and not generic)
|
|
84
|
+
if (file instanceof File && file.type && file.type !== 'application/octet-stream') {
|
|
85
|
+
return file.type;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Priority 3: Detect from filename extension
|
|
89
|
+
const fileName = typeof file === 'string' ? file : file instanceof File ? file.name : '';
|
|
90
|
+
if (fileName) {
|
|
91
|
+
const detectedType = getMimeTypeFromFileName(fileName);
|
|
92
|
+
if (detectedType) return detectedType;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fallback: Generic binary
|
|
96
|
+
return 'application/octet-stream';
|
|
97
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { storageAdapter } from '../lib/walrus/adapter.js';
|
|
2
|
+
import { loadEnv } from './env.js';
|
|
3
|
+
|
|
4
|
+
// Walrus aggregator URLs for direct HTTP access
|
|
5
|
+
const AGGREGATOR_URLS = {
|
|
6
|
+
testnet: 'https://aggregator.walrus-testnet.walrus.space',
|
|
7
|
+
mainnet: 'https://aggregator.walrus.space',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get the aggregator URL based on current network configuration
|
|
12
|
+
*/
|
|
13
|
+
function getAggregatorUrl(): string {
|
|
14
|
+
const env = loadEnv();
|
|
15
|
+
// Use environment override if available
|
|
16
|
+
if (env.walrusAggregator) {
|
|
17
|
+
return env.walrusAggregator;
|
|
18
|
+
}
|
|
19
|
+
// Default to testnet
|
|
20
|
+
const network = env.walrusNetwork === 'mainnet' ? 'mainnet' : 'testnet';
|
|
21
|
+
return AGGREGATOR_URLS[network];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate a preview URL by fetching directly from Walrus aggregator HTTP API
|
|
26
|
+
* This is faster than using the SDK as it makes a direct HTTP request
|
|
27
|
+
*
|
|
28
|
+
* @param blobId - The Walrus blob ID
|
|
29
|
+
* @param contentType - MIME type of the content (e.g., 'image/jpeg')
|
|
30
|
+
* @returns Object URL that can be used in <img> src
|
|
31
|
+
*/
|
|
32
|
+
async function generatePreviewUrlDirect(
|
|
33
|
+
blobId: string,
|
|
34
|
+
contentType: string
|
|
35
|
+
): Promise<string> {
|
|
36
|
+
const aggregatorUrl = getAggregatorUrl();
|
|
37
|
+
|
|
38
|
+
// Fetch blob data directly from aggregator
|
|
39
|
+
// Using the /v1/blobs/{blobId} endpoint which returns the full blob
|
|
40
|
+
const response = await fetch(`${aggregatorUrl}/v1/blobs/${blobId}`, {
|
|
41
|
+
headers: {
|
|
42
|
+
'Accept': 'application/octet-stream',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get blob data and create object URL
|
|
51
|
+
const blobData = await response.blob();
|
|
52
|
+
|
|
53
|
+
// Create a new blob with correct content type
|
|
54
|
+
const typedBlob = new Blob([blobData], { type: contentType });
|
|
55
|
+
const objectUrl = URL.createObjectURL(typedBlob);
|
|
56
|
+
|
|
57
|
+
return objectUrl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fallback: Generate preview using SDK download method
|
|
62
|
+
*/
|
|
63
|
+
async function generatePreviewUrlFallback(
|
|
64
|
+
blobId: string,
|
|
65
|
+
contentType: string
|
|
66
|
+
): Promise<string> {
|
|
67
|
+
// Download blob data as bytes via SDK
|
|
68
|
+
const bytes = await storageAdapter.download(blobId);
|
|
69
|
+
|
|
70
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
71
|
+
throw new Error('Expected Uint8Array from download');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create a Blob from the bytes with correct content type
|
|
75
|
+
const buffer = new ArrayBuffer(bytes.length);
|
|
76
|
+
const view = new Uint8Array(buffer);
|
|
77
|
+
view.set(bytes);
|
|
78
|
+
const blob = new Blob([buffer], { type: contentType });
|
|
79
|
+
|
|
80
|
+
// Create and return object URL
|
|
81
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
82
|
+
|
|
83
|
+
return objectUrl;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate a preview URL from blob ID
|
|
88
|
+
* Tries direct HTTP fetch first (faster), falls back to SDK if it fails
|
|
89
|
+
*
|
|
90
|
+
* @param blobId - The Walrus blob ID
|
|
91
|
+
* @param contentType - MIME type of the content (e.g., 'image/jpeg')
|
|
92
|
+
* @returns Object URL that can be used in <img> src
|
|
93
|
+
*/
|
|
94
|
+
export async function generatePreviewUrl(
|
|
95
|
+
blobId: string,
|
|
96
|
+
contentType: string
|
|
97
|
+
): Promise<string> {
|
|
98
|
+
try {
|
|
99
|
+
// Try direct HTTP fetch first (faster)
|
|
100
|
+
console.log(`[Preview] Attempting direct fetch for ${blobId.slice(0, 16)}...`);
|
|
101
|
+
const url = await generatePreviewUrlDirect(blobId, contentType);
|
|
102
|
+
console.log(`[Preview] Direct fetch successful`);
|
|
103
|
+
return url;
|
|
104
|
+
} catch (directError) {
|
|
105
|
+
console.warn(`[Preview] Direct fetch failed, falling back to SDK:`, directError);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Fallback to SDK method
|
|
109
|
+
const url = await generatePreviewUrlFallback(blobId, contentType);
|
|
110
|
+
console.log(`[Preview] SDK fallback successful`);
|
|
111
|
+
return url;
|
|
112
|
+
} catch (fallbackError) {
|
|
113
|
+
console.error(`[Preview] Both methods failed for ${blobId}:`, fallbackError);
|
|
114
|
+
throw fallbackError;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if content type is an image that can be displayed
|
|
121
|
+
*/
|
|
122
|
+
export function isImageType(contentType: string): boolean {
|
|
123
|
+
return contentType.startsWith('image/');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Revoke object URL to free memory
|
|
128
|
+
* Call this when the component unmounts or the URL is no longer needed
|
|
129
|
+
*/
|
|
130
|
+
export function revokePreviewUrl(url: string): void {
|
|
131
|
+
if (url.startsWith('blob:')) {
|
|
132
|
+
URL.revokeObjectURL(url);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -2,26 +2,38 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2020",
|
|
4
4
|
"useDefineForClassFields": true,
|
|
5
|
-
"lib": [
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
6
10
|
"module": "ESNext",
|
|
7
11
|
"skipLibCheck": true,
|
|
8
|
-
|
|
9
12
|
"moduleResolution": "bundler",
|
|
10
13
|
"allowImportingTsExtensions": true,
|
|
11
14
|
"resolveJsonModule": true,
|
|
12
15
|
"isolatedModules": true,
|
|
13
16
|
"noEmit": true,
|
|
14
17
|
"jsx": "react-jsx",
|
|
15
|
-
|
|
18
|
+
"types": [
|
|
19
|
+
"vite/client"
|
|
20
|
+
],
|
|
16
21
|
"strict": true,
|
|
17
22
|
"noUnusedLocals": true,
|
|
18
23
|
"noUnusedParameters": true,
|
|
19
24
|
"noFallthroughCasesInSwitch": true,
|
|
20
|
-
|
|
21
25
|
"paths": {
|
|
22
|
-
"@/*": [
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./src/*"
|
|
28
|
+
]
|
|
23
29
|
}
|
|
24
30
|
},
|
|
25
|
-
"include": [
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
"include": [
|
|
32
|
+
"src"
|
|
33
|
+
],
|
|
34
|
+
"references": [
|
|
35
|
+
{
|
|
36
|
+
"path": "./tsconfig.node.json"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## ==============================================
|
|
2
|
+
## Walrus Application - Environment Configuration
|
|
3
|
+
## ==============================================
|
|
4
|
+
|
|
5
|
+
## WALRUS NETWORK SETTINGS
|
|
6
|
+
## Network: testnet | mainnet | devnet
|
|
7
|
+
VITE_WALRUS_NETWORK=testnet
|
|
8
|
+
|
|
9
|
+
## Walrus Aggregator URL (for downloads)
|
|
10
|
+
VITE_WALRUS_AGGREGATOR=https://aggregator.walrus-testnet.walrus.space
|
|
11
|
+
|
|
12
|
+
## Walrus Publisher URL (for uploads)
|
|
13
|
+
VITE_WALRUS_PUBLISHER=https://publisher.walrus-testnet.walrus.space
|
|
14
|
+
|
|
15
|
+
## SUI BLOCKCHAIN SETTINGS
|
|
16
|
+
## Sui Network: testnet | mainnet | devnet
|
|
17
|
+
VITE_SUI_NETWORK=testnet
|
|
18
|
+
|
|
19
|
+
## Sui RPC URL (for wallet interactions)
|
|
20
|
+
VITE_SUI_RPC=https://fullnode.testnet.sui.io:443
|
|
21
|
+
|
|
22
|
+
## OPTIONAL FEATURES
|
|
23
|
+
## Blockberry Analytics API Key (leave empty to disable)
|
|
24
|
+
VITE_BLOCKBERRY_KEY=
|
|
25
|
+
|
|
26
|
+
## ==============================================
|
|
27
|
+
## PREREQUISITES
|
|
28
|
+
## ==============================================
|
|
29
|
+
## 1. Install Sui Wallet browser extension
|
|
30
|
+
## 2. Get testnet SUI from faucet: https://faucet.testnet.sui.io/
|
|
31
|
+
## 3. Copy this file to .env and fill in any optional values
|