Welcome!
In this repo you will find a basic Skynet app online to help you start developing on Skynet!
The goal of this workshop is to provide developers with examples of the important concepts of developing an app on Skynet.
Note: Local Development using MySky can run into issues if your browser security settings are set to "block third-party cookies and data." MySky isn't hosted at localhost, so it is considered "third-party" when your app isn't being run on Skynet. If you see this problem in Chrome or Brave, try visiting chrome://settings/cookies and adding localhost
to "Sites that can always use cookies."
Create React App is used for structuring the project and simplifying deployment, but you don't need any knowledge of React to follow the workshop.
- Open your terminal to the cloned repo folder and run
yarn install
to install the project's dependencies. - Run
yarn start
to see our app's starting layout. If your browser doesn't launch, visit localhost:3000. Create React App will auto-reload when you save files. (Use Ctrl+C in the terminal to stop your app.)
We'll first cover the most basic functionality of Skynet, uploading data.
Follow the steps below to update this app to allow the user to upload a file to Skynet. For this sample app, we'll ask the user to upload a picture.
- Install
skynet-js
by runningyarn add skynet-js
- First, you need to import the SDK and initialize a Skynet Client. Open the
file
src/App.js
, look for where Step 1.2 code goes, and paste the following code.
// Import the SkynetClient and a helper
import { SkynetClient } from 'skynet-js';
// We'll define a portal to allow for developing on localhost.
// When hosted on a skynet portal, SkynetClient doesn't need any arguments.
const portal =
window.location.hostname === 'localhost' ? 'https://siasky.net' : undefined;
// Initiate the SkynetClient
const client = new SkynetClient(portal);
- Next, create the upload functionality. In the
handleSubmit
function (called for when form is submitted), paste the code that will upload a file below the Step 1.3 mark.
// Upload user's file and get backs descriptor for our Skyfile
const { skylink } = await client.uploadFile(file);
// skylinks start with `sia://` and don't specify a portal URL
// we can generate URLs for our current portal though.
const skylinkUrl = await client.getSkylinkUrl(skylink);
console.log('File Uploaded:', skylinkUrl);
// To use this later in our React app, save the URL to the state.
setFileSkylink(skylinkUrl);
-
Above this code, uncomment
console.log('Uploading file...');
-
Test it out! If you aren't still running the app, run
yarn start
again and try uploading a file. If you open your Developer Console (by pressing F12), the console shows helpful messages.
In Part 1, our app successfully uploaded a file to Skynet, now we'll build on that code to upload a web page.
In addition to files, Skynet can receive directory uploads. Once uploaded to
Skynet, any directory with an index.html
will load in your browser just
like any website. This enables developers to write and deploy their web app,
just by uploading the project's build folder.
- First, create the upload directory functionality. Back in
handleSubmit
insidesrc/App.js
, paste this code in the area for Step 2.1.
// Create the text of an html file what will be uploaded to Skynet
// We'll use the skylink from Part 1 in the file to load our Skynet-hosted image.
const webPage = generateWebPage(name, skylinkUrl);
// Build our directory object, we're just including the file for our webpage.
const webDirectory = {
'index.html': webPage,
// 'couldList.jpg': moreFiles,
};
// Upload user's webpage
const { skylink: dirSkylink } = await client.uploadDirectory(
webDirectory,
'certificate'
);
// Generate a URL for our current portal
// We'll use a subdomain-style link
const dirSkylinkUrl = await client.getSkylinkUrl(dirSkylink, {
subdomain: true,
});
console.log('Web Page Uploaded:', dirSkylinkUrl);
// To use this later in our React app, save the URL to the state.
setWebPageSkylink(dirSkylink);
setWebPageSkylinkUrl(dirSkylinkUrl);
-
Above this code, uncomment
console.log('Uploading web page...');
-
Test it out! Now the user can submit their name and photo to generate their very own web page on Skynet!
In parts 1 and 2, you uploaded files onto Skynet. The files at these Skylinks are immutable, that is, they cannot be modified (or else their URL would also change). In this section, we'll use MySky to store editable data on Skynet that we can come back to and update.
Right now, if you hover over your image in the certificate, you get a nice green halo effect. But, we may want to change this later without changing our skylink. We can do this by saving some editable JSON data to Skynet and having our web page read the info directly from Skynet.
The first step is hooking up our app to MySky
, but you'll need a little theory here.
MySky lets users login across the entirety of the Skynet ecosystem. Once logged in, applications can request read and write access for specific "MySky Files" that have a file path. In this workshop, we'll be working with "Discoverable Files" -- these are files that anyone can see if they know your MySky UserID and the pathname for the file.
With MySky, we can write a JSON object to a path, and then tell someone our UserID and the pathname, and they can find the latest version of the data.
Let's setup MySky login and then look at writing data to Mysky files.
- First we need set the
dataDomain
we will request to from MySky. Replace the line of code in Step 3.1 with this.
// choose a data domain for saving files in MySky
const dataDomain = 'localhost';
- Now we need to add the code that will initialize MySky when the app loads. This code will also check to see if a user is already logged in with the appropriate permissions. Add the
following code to
src/App.js
for Step 3.2.
// define async setup function
async function initMySky() {
try {
// load invisible iframe and define app's data domain
// needed for permissions write
const mySky = await client.loadMySky(dataDomain);
// load necessary DACs and permissions
// await mySky.loadDacs(contentRecord);
// check if user is already logged in with permissions
const loggedIn = await mySky.checkLogin();
// set react state for login status and
// to access mySky in rest of app
setMySky(mySky);
setLoggedIn(loggedIn);
if (loggedIn) {
setUserID(await mySky.userID());
}
} catch (e) {
console.error(e);
}
}
// call async setup function
initMySky();
- Next, we need to handle when a user presses the "Login with MySky" button. This will create a pop-up window that will prompt the user to login. In future versions of MySky, it will also ask for additional permissions to be granted if the app doesn't already have them. Add the following code to
src/App.js
for Step 3.3.
// Try login again, opening pop-up. Returns true if successful
const status = await mySky.requestLoginAccess();
// set react state
setLoggedIn(status);
if (status) {
setUserID(await mySky.userID());
}
- We also need to handle when a user logs out. This method will tell MySky to log the user out of MySky and then delete any remaining user data from the application state. Add the
following code to
src/App.js
for Step 3.4.
// call logout to globally logout of mysky
await mySky.logout();
//set react state
setLoggedIn(false);
setUserID('');
-
Now that we've handled login and logout, we will return to saving data when a user submits the form. Locate and uncomment
console.log('Saving user data to MySky file...');
-
After uploading the image and webpage, we make a JSON object to save to MySky. Put the following code in the below Step 3.6.
// create JSON data to write to MySky
const jsonData = {
name,
skylinkUrl,
dirSkylink,
dirSkylinkUrl,
color: userColor,
};
// call helper function for MySky Write
await handleMySkyWrite(jsonData);
- We need to define what happens in the helper method above. We'll tell MySky to save our JSON file to our filePath. Put the following code in the below Step 3.7.
// Use setJSON to save the user's information to MySky file
try {
console.log('userID', userID);
console.log('filePath', filePath);
await mySky.setJSON(filePath, jsonData);
} catch (error) {
console.log(`error with setJSON: ${error.message}`);
}
- Next, we want the certificate web page to read this data. The code to fetch a MySky file is already in the generated page, but it needs to know our userID and the file path in order to find the JSON file. Find the code from Step 2.1 that says
const webPage = generateWebPage(name, skylinkUrl);
and replace it with
const webPage = generateWebPage(name, skylinkUrl, userID, filePath);
- Test it out! Now the user can select the color of the halo which is read from our MySky data! In the next step we'll add loading this data and editing it without re-uploading our image and webpage.
In part 3, we saw how to save mutable files on Skynet using MySky. In this section, we'll see how to load that data from MySky, and how to use the Content Record DAC to tell other you made new content or interacted with existing content.
DACs provider Javascript libraries that simplify interacting with the web app from your code.
- Install
content-record-library
by runningyarn add @skynetlabs/content-record-library
- Next we need to import the DAC. Look for where Step 4.2 code goes, and paste the following code.
import { ContentRecordDAC } from '@skynetlabs/content-record-library';
- Now, we'll create a
contentRecord
object, used to call methods against the Content Record DAC's API. For Step 4.3, paste the following code.
const contentRecord = new ContentRecordDAC();
- We need to tell MySky to load our DAC. This also informs it of the permissions our DAC will need for a successful login. Return to the code from Step 3.2 and uncomment the following code.
await mySky.loadDacs(contentRecord);
- Let's wire up our "Load Data" button, by grabbing data from MySky in the
loadData
method. Add the following code for Step 4.5.
// Use getJSON to load the user's information from SkyDB
const { data } = await mySky.getJSON(filePath);
// To use this elsewhere in our React app, save the data to the state.
if (data) {
setName(data.name);
setFileSkylink(data.skylinkUrl);
setWebPageSkylink(data.dirSkylink);
setWebPageSkylinkUrl(data.dirSkylinkUrl);
setUserColor(data.color);
console.log('User data loaded from SkyDB!');
} else {
console.error('There was a problem with getJSON');
}
- Here, we'll add logic for saving edited content to MySky without re-uploading our image and directory. Then, we'll call the Content Record DAC and have it record that we "interacted" with this skylink. This will occur when the "Save Data and Record Update Action" button is pressed, triggering the
handleSaveAndRecord
method. Add the following code at Step 4.6.
console.log('Saving user data to MySky');
const jsonData = {
name,
skylinkUrl: fileSkylink,
dirSkylink: webPageSkylink,
dirSkylinkUrl: webPageSkylinkUrl,
color: userColor,
};
try {
// write data with MySky
await mySky.setJSON(filePath, jsonData);
// Tell contentRecord we updated the color
await contentRecord.recordInteraction({
skylink: webPageSkylink,
metadata: { action: 'updatedColorOf' },
});
} catch (error) {
console.log(`error with setJSON: ${error.message}`);
}
- Returning to our logic from before, we want to tell the Content Record DAC that we've created a new certificate when we upload a webpage. Add the following code for Step 4.7.
try {
await contentRecord.recordNewContent({
skylink: jsonData.dirSkylink,
});
} catch (error) {
console.log(`error with CR DAC: ${error.message}`);
}
- Test it out! Now the user can update the color of the halo which is read from our MySky data! You can also use the Content Record Viewer tool to see your content record.
Congratulations! You have a fully functioning Skapp! Let's deploy it and let the world see its wonder! As we mentioned before, deploying an application is as easy as uploading a directory.
-
For Create React App projects, we need to add
"homepage": ".",
to thepackage.json
. -
Next, we'll return to where we initialized the
SkynetClient
in Step 1.2. When deployed to Skynet, we don't want our App to only communicate with siasky.net, instead we want it to communicate with the portal the app is being served from. Find the lines we used to initialize the Skynet Client:
const portal =
window.location.hostname === 'localhost' ? 'https://siasky.net' : undefined;
const client = new SkynetClient(portal);
In a production app, you don't need any arguments here (which in JS is the same as passing undefined
as an argument):
const client = new SkynetClient();
-
Build the application with
yarn build
-
Upload the newly created
build
folder to https://siasky.net. (Make sure you select 'Do you want to upload an entire directory?') -
Now any of your friends can make their own certificates!
Now that you've deployed a Skynet app, there's many things to keep learning!
-
You can learn how to use Handshake for a decentralized human-readable URL like skyfeed.hns.siasky.net.
-
You can automate deployment of your site using a Github Action.
We're always improving our Skynet Developer Resources, so check that out and join our Discord to share ideas with other devs.
In the project directory, you can run:
yarn start
Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
yarn build
Builds the app for production to the build
folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.