Skip to content

Commit

Permalink
Merge pull request #7 from ltfschoen/luke/save-website-filecoin
Browse files Browse the repository at this point in the history
wip: Try to save current Website IPFS Hash to FFS storage
  • Loading branch information
ltfschoen authored Jul 16, 2020
2 parents dfe2b85 + 47eff10 commit 1f8f294
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 14 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ HackFS team https://hack.ethglobal.co/hackfs/teams/recpspjxSRMexZJVg/recHa78c3ed
* [X] Create and view multiple Filecoin Addresses and associate them with PowerGate token.
* [X] Retrieve Pin (IPFS Hash where front-end deployed) from PinList using Express.js API endpoint that connects to Pinata (http://ethquad.herokuapp.com/api/getWebsiteIPFSHash)
* [X] Display latest Pin (Website IPFS Hash) in front-end
* [ ] Modify the Slate's "Make a Storage Deal" code to deploy that latest Pin to Filecoin Testnet (not just Local)
* [ ] Modify the Slate's "Make a Storage Deal" code to deploy that latest Pin to Lotus Filecoin Testnet (not just Local)
* [ ] Pending resolution of issues:
* https://github.com/filecoin-project/slate/issues/71
* https://github.com/textileio/powergate/issues/521
* https://github.com/ltfschoen/ethquad/pull/7
* [X] Frontend deployed to IPFS Address using Pinata SDK
* [X] Request decentralised domain name from Unstoppable Domains (https://ethquad.crypto)
* [X] Deployment script generate a new Pin and Unpins all previous (./scripts/pinataUploadIpfs.js)
* [X] Preview using Official IPFS Gateway (i.e. https://ipfs.io/ipfs/<IPFS_HASH>) in development environment (`yarn dev:ipfs:preview`)
* [ ] Configure domain name to redirect to Heroku (where it further redirects to the IPFS hash)
* Pending assistance from Slack group #hfs-sponsor-unstoppable-team https://filecoinproject.slack.com/archives/C016UAP2N8Z/p1594788644226200
* [X] Configure domain name to redirect to Heroku (where it further redirects to the IPFS hash)
* Pending name server changes to propagate https://filecoinproject.slack.com/archives/C016UAP2N8Z/p1594877963245700
* [ ] Backend Express.js API connection to Infura Eth 2.0 Endpoint (https://altona.infura.io)

### Optional
Expand Down
116 changes: 107 additions & 9 deletions client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { Component } from 'react';
import * as System from 'slate-react-system';
import { createPow } from "@textile/powergate-client";
import { Alert, Container, Col, Row, Spinner } from "react-bootstrap";
import { Alert, Button, Container, Col, Row, Spinner } from "react-bootstrap";
import { toHexString } from './helpers';
import { Greeter } from './components/Greeter';
import './App.css';

Expand Down Expand Up @@ -177,8 +178,98 @@ class App extends Component {
}
}

handleCreateFilecoinStorageDeal = () => {
// Store Website IPFS Hash on Filecoin
// Reference: https://github.com/filecoin-project/slate/blob/main/pages/experiences/make-storage-deal.js
handleCreateFilecoinStorageDeal = async () => {
const { websiteIPFSHash } = this.state;
console.log('handleCreateFilecoinStorageDeal');
// FIXME - how to check if any cid's exist in FFS without having to use a
// try/catch block so it doesn't crash with error
// `Uncaught (in promise) Error: stored item not found` if no items exist?
let showAll;
try {
// List cid infos for all data stored in the current FFS instance
showAll = await this.PowerGate.ffs.showAll();
console.log('Show all cid info');
console.log('showAll: ', showAll);
} catch (error) {
// Store item may not have been found
console.error('showAll error: ', error);
console.log('Show all cid info failed store item not found');
}

// FIXME - how to check if a specific cid exists in FFS without having to use a
// try/catch block so it doesn't crash with error if we try to remove it
// `Uncaught (in promise) Error: stored item not found` but when it doesn't exist?
// Note that if we try using `ffs.get(cid)` and it doesn't exist then it also
// crashes with that same error, so we need to use a try/catch block with that too.
try {
// Remove existing cid (if any) for the Website IPFS Hash from FFS storage
const removed = await this.PowerGate.ffs.remove(websiteIPFSHash);
console.log('removed', removed);
} catch (error) {
// Store item may not have been found
console.error('remove error: ', error);
console.log('Remove cid failed since it does not exist');
}

try {
// Cache data in IPFS in preparation for storing data in FFS by calling pushConfig
// e.g. cid: QmQbdEo1K15gHCrPz4jcLuL7hDLy5mJeFKE6mmKXXeUth4
const { cid } = await this.PowerGate.ffs.addToHot(websiteIPFSHash);
console.log('Cached cid data in IPFS in preparation for storing data in FFS');
console.log('cid: ', cid);

// FIXME - how to use FFS to check if a cid already exists before running
// `ffs.pushConfig(cid)` without it crashing when we call `ffs.get(cid)`
// but it doesn't exist (e.g. why not add a new FFS function `isCid`?)
// It appears to be necessary to do this, otherwise if you try to call
// `ffs.pushConfig(cid)` when the cid already exists, it crashes with
// error `cid may have already been pinned, consider using override flag`
// but where do we find out how to use the override flag?

// Push a storage config for the specified cid
const { jobId } = await this.PowerGate.ffs.pushConfig(cid);
console.log('Pushing cached cid data from IPFS to FFS');
console.log('jobId: ', jobId);

const cancel = this.PowerGate.ffs.watchJobs((job) => {
console.log('Job update: ', job);

// FIXME - Save cid to state when get confirmation it is stored in FFS.
// Buth how do we detect when it's been successfully stored to FFS Storage?

// this.setState({
// cid,
// cidDataHexStr
// });
}, jobId);
console.log('cancel: ', cancel);

// FIXME - cidData (e.g. `42650d43746dd706dd531ca7bc36dacb0c4c0cc7ab6d69d9019299aa71c1f70156d7`)
// does not match the cid value (e.g. the Website IPFS Address of
// `QmUNQ3Rt1wbdUxynvDbaywxMDMerbWnZAZKZqnHB9wFW15`)

// Show the data stored for the current cid
const cidData = await this.PowerGate.ffs.get(cid);
const cidDataHexStr = toHexString(cidData);
console.log('Retrieved cached cid data from IPFS');
console.log('cidData: ', cidDataHexStr);
} catch (error) {
console.error('error: ', error);
}

// FIXME - showAll is empty array even though cid has been created in cache using addToHot
try {
// List cid infos for all data stored in the current FFS instance
showAll = await this.PowerGate.ffs.showAll();
console.log('Show all cid info');
console.log('showAll: ', showAll);
} catch (error) {
// Store item may not have been found
console.error('showAll error: ', error);
console.log('Show all cid info failed store item not found');
}
}

render() {
Expand Down Expand Up @@ -211,6 +302,20 @@ class App extends Component {
}
</Col>
</Row>
{ websiteIPFSHash ? (
<Row className="justify-content-md-center">
<Col xs={12} md={12}>
<Button
size='lg'
style={{backgroundColor: '#2935ff', fontSize: '0.75rem'}}
onClick={this.handleCreateFilecoinStorageDeal}
>
Save Website IPFS Hash to Filecoin Storage
</Button>
</Col>
</Row>
) : null
}
<Row className="justify-content-md-center">
<Col xs={12} md={12}>
{ info ? (
Expand All @@ -224,13 +329,6 @@ class App extends Component {
/>
</Col>
</Row>
<Row className="justify-content-md-center">
<Col xs={12} md={12}>
<System.CreateFilecoinStorageDeal
onSubmit={this.handleCreateFilecoinStorageDeal}
/>
</Col>
</Row>
</Container>
);
}
Expand Down
34 changes: 34 additions & 0 deletions client/src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Source: https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
function arrayBufferToString(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}

function stringToArrayBuffer(str) {
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}

function toByteArray(hexString) {
var result = [];
for (var i = 0; i < hexString.length; i += 2) {
result.push(parseInt(hexString.substr(i, 2), 16));
}
return result;
}

function toHexString(byteArray) {
return Array.prototype.map.call(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}

export {
arrayBufferToString,
stringToArrayBuffer,
toByteArray,
toHexString,
}
8 changes: 6 additions & 2 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { connectToPinata } = require('../helpers/connectToPinata');

const app = express();
const port = process.env.PORT || 5000;
const isProd = process.env.NODE_ENV === 'production';

const corsWhitelist = [
'http://localhost:5000',
Expand All @@ -18,12 +19,15 @@ const corsOptions = {
'allowedHeaders': ['Content-Type'],
'exposedHeaders': ['Content-Type'],
'origin': function (origin, callback) {
if (corsWhitelist.indexOf(origin) !== -1) {
// Do not want to block REST tools or server-to-server requests
// when running with `yarn dev` on localhost:3000
if (corsWhitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
// "origin": "*",
'methods': 'GET,HEAD'
};

Expand All @@ -47,7 +51,7 @@ app.use((err, req, res, next) => {
app.get('/api/getWebsiteIPFSHash', cors(corsOptions),
// Middleware chain
async (req, res, next) => {
console.log('/api/getWebsiteIPFSHash');
console.log('Received GET request at API endpoint /api/getWebsiteIPFSHash');
console.log('Connecting to Pinata');
pinata = await connectToPinata();
if (pinata) {
Expand Down

0 comments on commit 1f8f294

Please sign in to comment.