@blockfact/react-facti 1.0.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/README.md +185 -0
- package/index.d.ts +35 -0
- package/index.js +138 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# @blockfact/react-facti
|
|
2
|
+
|
|
3
|
+
React component for displaying and verifying .facti files with blockchain verification.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @blockfact/react-facti
|
|
9
|
+
# or
|
|
10
|
+
yarn add @blockfact/react-facti
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Basic Display
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import FactiImage from '@blockfact/react-facti';
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<FactiImage
|
|
23
|
+
factiUrl="https://gateway.pinata.cloud/ipfs/QmXXX"
|
|
24
|
+
alt="Verified Content"
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### With Metadata
|
|
31
|
+
|
|
32
|
+
```jsx
|
|
33
|
+
<FactiImage
|
|
34
|
+
factiUrl="https://gateway.pinata.cloud/ipfs/QmXXX"
|
|
35
|
+
showMetadata={true}
|
|
36
|
+
onMetadataLoad={(metadata) => {
|
|
37
|
+
console.log('Transaction:', metadata.tx_hash);
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Custom Styling
|
|
43
|
+
|
|
44
|
+
```jsx
|
|
45
|
+
<FactiImage
|
|
46
|
+
factiUrl="https://gateway.pinata.cloud/ipfs/QmXXX"
|
|
47
|
+
className="my-image"
|
|
48
|
+
style={{ maxWidth: '500px', margin: '20px auto' }}
|
|
49
|
+
/>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Parse Manually
|
|
53
|
+
|
|
54
|
+
```jsx
|
|
55
|
+
import { parseFacTi } from '@blockfact/react-facti';
|
|
56
|
+
|
|
57
|
+
function CustomComponent() {
|
|
58
|
+
const [data, setData] = useState(null);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
parseFacTi('https://gateway.pinata.cloud/ipfs/QmXXX')
|
|
62
|
+
.then(setData);
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
if (!data) return <div>Loading...</div>;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<img src={data.imageUrl} alt="Content" />
|
|
70
|
+
<p>Owner: {data.metadata.wallet}</p>
|
|
71
|
+
<p>Location: {data.metadata.latitude}, {data.metadata.longitude}</p>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Default | Description |
|
|
80
|
+
|------|------|---------|-------------|
|
|
81
|
+
| `factiUrl` | string | required | URL to .facti file |
|
|
82
|
+
| `alt` | string | `'BlockFact Content'` | Image alt text |
|
|
83
|
+
| `className` | string | `''` | CSS class name |
|
|
84
|
+
| `style` | object | `{}` | Inline styles |
|
|
85
|
+
| `showMetadata` | boolean | `false` | Show metadata below image |
|
|
86
|
+
| `onMetadataLoad` | function | `null` | Callback when metadata loads |
|
|
87
|
+
| `onError` | function | `null` | Callback on error |
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### `parseFacTi(factiUrl)`
|
|
92
|
+
|
|
93
|
+
Parse a .facti file and extract image + metadata.
|
|
94
|
+
|
|
95
|
+
**Returns:**
|
|
96
|
+
```javascript
|
|
97
|
+
{
|
|
98
|
+
imageUrl: "blob:...", // Blob URL for image
|
|
99
|
+
metadata: {
|
|
100
|
+
tx_hash: "0x...",
|
|
101
|
+
wallet: "0x...",
|
|
102
|
+
timestamp: "2026-02-26T13:20:00Z",
|
|
103
|
+
latitude: 40.7128,
|
|
104
|
+
longitude: -74.0060,
|
|
105
|
+
// ... other fields
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## TypeScript
|
|
111
|
+
|
|
112
|
+
Full TypeScript support included:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import FactiImage, { FactiMetadata, parseFacTi } from '@blockfact/react-facti';
|
|
116
|
+
|
|
117
|
+
function App() {
|
|
118
|
+
const handleLoad = (metadata: FactiMetadata) => {
|
|
119
|
+
console.log(metadata.tx_hash);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return <FactiImage factiUrl="..." onMetadataLoad={handleLoad} />;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Features
|
|
127
|
+
|
|
128
|
+
- ✅ Zero dependencies (only React peer dependency)
|
|
129
|
+
- ✅ TypeScript support
|
|
130
|
+
- ✅ Automatic image extraction from .facti files
|
|
131
|
+
- ✅ Blockchain verification links
|
|
132
|
+
- ✅ GPS coordinates with Google Maps links
|
|
133
|
+
- ✅ Loading and error states
|
|
134
|
+
- ✅ Memory cleanup (revokes blob URLs)
|
|
135
|
+
|
|
136
|
+
## Examples
|
|
137
|
+
|
|
138
|
+
### Gallery
|
|
139
|
+
|
|
140
|
+
```jsx
|
|
141
|
+
const images = [
|
|
142
|
+
'https://gateway.pinata.cloud/ipfs/QmAAA',
|
|
143
|
+
'https://gateway.pinata.cloud/ipfs/QmBBB',
|
|
144
|
+
'https://gateway.pinata.cloud/ipfs/QmCCC',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
function Gallery() {
|
|
148
|
+
return (
|
|
149
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '20px' }}>
|
|
150
|
+
{images.map(url => (
|
|
151
|
+
<FactiImage key={url} factiUrl={url} showMetadata={true} />
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### With Error Handling
|
|
159
|
+
|
|
160
|
+
```jsx
|
|
161
|
+
function SafeFactiImage({ factiUrl }) {
|
|
162
|
+
const [error, setError] = useState(null);
|
|
163
|
+
|
|
164
|
+
if (error) {
|
|
165
|
+
return <div>Failed to load: {error.message}</div>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<FactiImage
|
|
170
|
+
factiUrl={factiUrl}
|
|
171
|
+
onError={setError}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
180
|
+
|
|
181
|
+
## Links
|
|
182
|
+
|
|
183
|
+
- [npm](https://www.npmjs.com/package/@blockfact/react-facti)
|
|
184
|
+
- [GitHub](https://github.com/blockfact/react-facti)
|
|
185
|
+
- [BlockFact](https://blockfact.io)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FactiMetadata {
|
|
4
|
+
tx_hash: string;
|
|
5
|
+
wallet: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
latitude: number;
|
|
8
|
+
longitude: number;
|
|
9
|
+
session_id: string;
|
|
10
|
+
poseidon_hash: string;
|
|
11
|
+
owner_address: string;
|
|
12
|
+
created_at: string;
|
|
13
|
+
finalized_at: string;
|
|
14
|
+
mime: string;
|
|
15
|
+
version: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ParsedFacti {
|
|
19
|
+
imageUrl: string;
|
|
20
|
+
metadata: FactiMetadata;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FactiImageProps {
|
|
24
|
+
factiUrl: string;
|
|
25
|
+
alt?: string;
|
|
26
|
+
className?: string;
|
|
27
|
+
style?: React.CSSProperties;
|
|
28
|
+
showMetadata?: boolean;
|
|
29
|
+
onMetadataLoad?: (metadata: FactiMetadata) => void;
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parseFacTi(factiUrl: string): Promise<ParsedFacti>;
|
|
34
|
+
|
|
35
|
+
export default function FactiImage(props: FactiImageProps): JSX.Element;
|
package/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse .facti file from URL
|
|
5
|
+
* @param {string} factiUrl - URL to .facti file
|
|
6
|
+
* @returns {Promise<{imageUrl: string, metadata: object}>}
|
|
7
|
+
*/
|
|
8
|
+
export async function parseFacTi(factiUrl) {
|
|
9
|
+
const response = await fetch(factiUrl);
|
|
10
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
11
|
+
const data = new Uint8Array(arrayBuffer);
|
|
12
|
+
|
|
13
|
+
if (data[0] !== 0xFA || data[1] !== 0x49 || data[2] !== 0x01 || data[3] !== 0x00) {
|
|
14
|
+
throw new Error('Invalid .facti file format');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const metadataLength = new DataView(arrayBuffer, 36, 4).getUint32(0);
|
|
18
|
+
const metadataBytes = data.slice(40, 40 + metadataLength);
|
|
19
|
+
const metadata = JSON.parse(new TextDecoder().decode(metadataBytes));
|
|
20
|
+
|
|
21
|
+
const imageData = data.slice(40 + metadataLength);
|
|
22
|
+
const blob = new Blob([imageData], { type: metadata.mime || 'image/png' });
|
|
23
|
+
const imageUrl = URL.createObjectURL(blob);
|
|
24
|
+
|
|
25
|
+
return { imageUrl, metadata };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* FactiImage Component
|
|
30
|
+
* Displays .facti file with blockchain verification
|
|
31
|
+
*/
|
|
32
|
+
export default function FactiImage({
|
|
33
|
+
factiUrl,
|
|
34
|
+
alt = 'BlockFact Content',
|
|
35
|
+
className = '',
|
|
36
|
+
style = {},
|
|
37
|
+
showMetadata = false,
|
|
38
|
+
onMetadataLoad,
|
|
39
|
+
onError
|
|
40
|
+
}) {
|
|
41
|
+
const [imageUrl, setImageUrl] = useState(null);
|
|
42
|
+
const [metadata, setMetadata] = useState(null);
|
|
43
|
+
const [loading, setLoading] = useState(true);
|
|
44
|
+
const [error, setError] = useState(null);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let mounted = true;
|
|
48
|
+
|
|
49
|
+
async function load() {
|
|
50
|
+
try {
|
|
51
|
+
setLoading(true);
|
|
52
|
+
const { imageUrl, metadata } = await parseFacTi(factiUrl);
|
|
53
|
+
|
|
54
|
+
if (mounted) {
|
|
55
|
+
setImageUrl(imageUrl);
|
|
56
|
+
setMetadata(metadata);
|
|
57
|
+
if (onMetadataLoad) onMetadataLoad(metadata);
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (mounted) {
|
|
61
|
+
setError(err.message);
|
|
62
|
+
if (onError) onError(err);
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
if (mounted) setLoading(false);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
load();
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
mounted = false;
|
|
73
|
+
if (imageUrl) URL.revokeObjectURL(imageUrl);
|
|
74
|
+
};
|
|
75
|
+
}, [factiUrl]);
|
|
76
|
+
|
|
77
|
+
if (loading) {
|
|
78
|
+
return <div className={className} style={style}>Loading...</div>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (error) {
|
|
82
|
+
return <div className={className} style={{ ...style, color: '#f44336' }}>Error: {error}</div>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className={className} style={style}>
|
|
87
|
+
<img
|
|
88
|
+
src={imageUrl}
|
|
89
|
+
alt={alt}
|
|
90
|
+
style={{
|
|
91
|
+
maxWidth: '100%',
|
|
92
|
+
border: '2px solid #4CAF50',
|
|
93
|
+
borderRadius: '4px'
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{showMetadata && metadata && (
|
|
98
|
+
<div style={{
|
|
99
|
+
background: '#f5f5f5',
|
|
100
|
+
padding: '15px',
|
|
101
|
+
borderRadius: '5px',
|
|
102
|
+
marginTop: '10px',
|
|
103
|
+
fontSize: '13px'
|
|
104
|
+
}}>
|
|
105
|
+
<div style={{ fontWeight: 'bold', marginBottom: '10px' }}>📋 Metadata</div>
|
|
106
|
+
<div style={{ margin: '5px 0' }}>
|
|
107
|
+
<strong>Transaction:</strong>{' '}
|
|
108
|
+
<a
|
|
109
|
+
href={`https://sepolia.voyager.online/tx/${metadata.tx_hash}`}
|
|
110
|
+
target="_blank"
|
|
111
|
+
rel="noopener noreferrer"
|
|
112
|
+
style={{ color: '#4CAF50' }}
|
|
113
|
+
>
|
|
114
|
+
{metadata.tx_hash?.slice(0, 20)}...
|
|
115
|
+
</a>
|
|
116
|
+
</div>
|
|
117
|
+
<div style={{ margin: '5px 0' }}>
|
|
118
|
+
<strong>Owner:</strong> {metadata.wallet?.slice(0, 20)}...
|
|
119
|
+
</div>
|
|
120
|
+
<div style={{ margin: '5px 0' }}>
|
|
121
|
+
<strong>Timestamp:</strong> {metadata.timestamp}
|
|
122
|
+
</div>
|
|
123
|
+
<div style={{ margin: '5px 0' }}>
|
|
124
|
+
<strong>Location:</strong>{' '}
|
|
125
|
+
<a
|
|
126
|
+
href={`https://www.google.com/maps?q=${metadata.latitude},${metadata.longitude}`}
|
|
127
|
+
target="_blank"
|
|
128
|
+
rel="noopener noreferrer"
|
|
129
|
+
style={{ color: '#4CAF50' }}
|
|
130
|
+
>
|
|
131
|
+
{metadata.latitude}, {metadata.longitude}
|
|
132
|
+
</a>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blockfact/react-facti",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React component for displaying and verifying .facti files",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"No tests yet\""
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"blockfact",
|
|
12
|
+
"facti",
|
|
13
|
+
"blockchain",
|
|
14
|
+
"verification",
|
|
15
|
+
"react",
|
|
16
|
+
"image"
|
|
17
|
+
],
|
|
18
|
+
"author": "BlockFact",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/blockfact/react-facti.git"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=16.8.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.js",
|
|
29
|
+
"index.d.ts",
|
|
30
|
+
"README.md"
|
|
31
|
+
]
|
|
32
|
+
}
|