This will be a step-by-step guide for uploading a file to the network and be able to view it in SAFE browser.
You can always simply use web hosting manager to upload files to the network, however, if you go through this tutorial, you’ll learn a good chunk of the SAFE web api.
Download latest release of SAFE Browser: Release SAFE Browser 0.4.0 · maidsafe/sn_browser · GitHub
Clone this repository and cd into safe_web_api_playground
: safe_examples/safe_web_api_playground at master · maidsafe-archive/safe_examples · GitHub
Get the safe_web_api_playground
running on localhost:3003
then access it in your SAFE Browser tab using localhost://p:3003
.
Reference documentation: http://docs.maidsafe.net/beaker-plugin-safe-app/
The code examples I provide are meant for a step-by-step process and will be more verbose than is needed for ES6 in a program.
Overview
We’ll start with an example SAFE url where we’d like to be able to visit to view our uploaded html file:
safe://site.mypublicid
mypublicid
is our public ID, and site
is the service name.
A service can be an email address, chat ID, or in this case, site
is a web service that holds file entries which are loaded into the browser.
We will:
- Create a mutable data structure with a randomly generated name, type tag
15002
, and insert file entry
- Create mutable data structure, named after the hashed version of our public id,
mypublicid
, with type tag15001
. Insert an entry where the key is `site, and the value is the name of the MD structure created in step 1. - Get
_publicNames
container and insert encrypted entry for key is the encryptedmypublicid
and the value is the encrypted hash ofmypublicid
- Get
_public
container and insert entry where key is, in this case,_public/mypublicid/site-root
and the value is name of the MD structure created in step 1.
Step 1: Initialise (cheers to Scotland), application
let appInfo = {
id: 'net.maidsafe.test_site.webclient.1',
name: 'Test site',
vendor: 'MaidSafe Ltd.',
scope: null
};
window.safeApp.initialise(appInfo)
.then(function(res) {
appHandle = res;
return 'Returns app token: ' + res;
});
There is no convention so far for id
, name
, and vendor
, name them as you will.
Step 2: Ask for app authorization and permissions
window.safeApp.authorise(
appHandle,
{
_public: [
'Read',
'Insert',
'Update',
'Delete'
],
_publicNames: [
'Read',
'Insert',
'Update',
'Delete'
]
},
{own_container: true}
).then((res) => {
authUri = res;
return 'App was authorised and auth URI received: ' + res;
});
own_container
set to true
will create private app container known as home container. We’ll take a took at it later.
_publicNames
is a container created for your user account and will follow you around the network. In this tutorial, it will store the encrypted version of your public ID. It will other applications to lookup your public ID and other information intended to be shared across applications on the network.
_public
container is created to hold non encrypted entries, in our case, a name reference to our file entry MD.
Step 3: Connect app to the network
window.safeApp.connectAuthorised(appHandle, authUri)
.then(appHandle => {
return 'The app was authorised and a session was created with the network. App token returned: ' + appHandle;
});
Step 4: Create MD to hold file entries
window.safeMutableData.newRandomPublic(appHandle, 15002)
.then((res) => {
mdHandle = res;
return 'Returns handle to newly created, public, randomly named MutableData structure: ' + res;
});
The 15002
tag is the network convention for a web service. In the case of an email inbox, 15003
will be used. These conventions are important for cross application compatibility.
Step 5: Setup MD
In order to save this MD we need three objects: permissions, permissions-set, and entries
window.safeMutableData.newPermissions(appHandle)
.then((res) => {
permsHandle = res;
return 'Newly created permissions handle returned: ' + res;
});
window.safeMutableData.newPermissionSet(appHandle)
.then((res) => {
permSetHandle = res;
return 'Returns newly created PermissionsSet handle: ' + res;
});
window.safeMutableData.newEntries(appHandle)
.then((res) => {
entriesHandle = res;
return 'Returns an entries handle to be used with safeMutableDataEntries functions: ' + res;
});
Here handles are returned to be able to run operations on our permissions, permissions-set, and entries objects.
We’ll now set allowable actions in our permissions-set:
window.safeMutableDataPermissionsSet.setAllow(permSetHandle, "Insert")
.then(() => window.safeMutableDataPermissionsSet.setAllow(permSetHandle, "Update"))
.then(() => window.safeMutableDataPermissionsSet.setAllow(permSetHandle, "Delete"))
Obtain public signing key to insert that permissions-set into our permissions object:
window.safeCrypto.getAppPubSignKey(appHandle)
.then((res) => {
signKeyHandle = res;
return 'Returns applications public signing key: ' + res;
});
Insert permissions-set into our permissions object:
window.safeMutableDataPermissions.insertPermissionsSet(permsHandle, signKeyHandle, permSetHandle)
.then(_ => {
return 'Finished inserting new permissions';
});
Step 6: Save to the network
window.safeMutableData.put(mdHandle, permsHandle, entriesHandle)
.then(_ => {
return 'Finished creating and committing MutableData to the network';
});
Step 7: Insert files into service MD
window.safeMutableData.emulateAs(mdHandle, 'NFS')
.then((res) => {
nfsHandle = res;
return 'Returns nfsHandle: ' + res;
});
In the SAFE web API playground, you can choose a file with file explorer. Here we’ll simply insert our own string content:
let fileContent = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>SAFE web site</title></head><body>SAFE web site</body></html>';
return window.safeNfs.create(nfsHandle, fileContent)
.then((res) => {
fileHandle = res;
return 'Returns the file handle of a newly created file: ' + res;
});
Then commit the file to the network:
let fileName = 'index.html';
return window.safeNfs.insert(nfsHandle, fileHandle, fileName)
.then(res => {
fileHandle = res;
return 'Returns same fileHandle: ' + res;
});
Step 8: Save name of randomly named MD
window.safeMutableData.getNameAndTag(mdHandle)
.then((res) => {
mdName = res.name.buffer;
return 'Name: ' + String.fromCharCode.apply(null, new Uint8Array(res.name.buffer)) + ', Tag: ' + res.tag;
});
Step 9: Free handles from memory:
window.safeCryptoSignKey.free(signKeyHandle)
.then(_ => {
signKeyHandle = null;
return 'signKeyHandle freed from memory';
});
window.safeMutableDataPermissionsSet.free(permSetHandle)
.then(_ => {
permSetHandle = null;
return 'permissionsSetHandle is freed from memory';
});
window.safeMutableDataPermissions.free(permsHandle)
.then(_ => {
permsHandle = null;
return 'Frees permsHandle from memory'
});
window.safeMutableDataEntries.free(entriesHandle)
.then(_ => {
entriesHandle = null;
return 'Handle to MutableData entries freed from memory';
});
window.safeMutableData.free(mdHandle)
.then(_ => {
mdHandle = null;
return 'MutableData is freed from memory';
});
All done with creating the service MD srtucture that holds our file entries.
Step 10: Create public ID and insert service
First, hash your desired public ID:
window.safeCrypto.sha3Hash(appHandle, 'mypublicid')
.then((res) => {
hashedString = res;
return 'SHA3 Hash generated: ', String.fromCharCode.apply(null, new Uint8Array(res));
});
Create new mutable data structure named after your hashed public id:
window.safeMutableData.newPublic(appHandle, hashedString, 15001)
.then((res) => {
mdHandle = res;
return 'Returns handle to newly created or already existing, public, explicitly named Mutable Data structure: ' + res;
});
The 15001
tag designates this MD as a DNS lookup for services.
Now go through the same setup process as in Step 5 and come back here when you’re done.
We’ll now insert our service:
window.safeMutableDataEntries.insert(entriesHandle, 'site', mdName)
.then(_ => 'New entry inserted');
Observe that the entry key is the service name, site
, and the entry value is the name of our service MD, created in Step 4.
Repeat Step 6 to commit this MD to the network
Step 11: View file on network
At this point you can observe successful file upload in two ways:
- Visit
safe://site.mypublicid
in your SAFE browser
or fetch the file:
window.safeApp.webFetch(
appHandle,
'safe://site.mypublicid/index.html' // the SAFE Network URL
)
.then((data) => {
console.log(String.fromCharCode.apply(null, new Uint8Array(data)));
return String.fromCharCode.apply(null, new Uint8Array(data));
});
Repeat Step 9 to free handles from memory
Step 12: Insert public ID into _publicNames
container
Get _publicNames
container:
let container = '_publicNames';
return window.safeApp.getContainer(appHandle, container)
.then((res) => {
mdHandle = res;
return 'Returns handle to Mutable Data behind ' + container + ' container: ' + res;
});
Get mutation object:
window.safeMutableData.newMutation(appHandle)
.then((res) => {
mutationHandle = res;
return 'Returns handle to be able to call safeMutableDataMutation functions: ' + res;
});
Encrypt plain text version of public id:
window.safeMutableData.encryptKey(mdHandle, 'mypublicid')
.then((res) => {
encryptedKey = res;
return 'Encrypted key: ' + res;
});
Encrypt hashed version of public id. This should still be stored in your hashedString
variable. Otherwise use window.safeCrypto.sha3Hash
as we did in Step 10.
window.safeMutableData.encryptValue(mdHandle, hashedString)
.then((res) => {
encryptedValue = res;
return 'Encrypted value: ' + res;
});
Insert entry into mutation object:
window.safeMutableDataMutation.insert(mutationHandle, encryptedKey, encryptedValue)
.then(_ => {
return 'Registers an insert operation with mutation handle, later to be applied.';
// You must now run safeMutableData.applyEntriesMutation(mdHandle, mutationHandle) to save changes.
});
Apply mutation to _publicNames
container:
window.safeMutableData.applyEntriesMutation(mdHandle, mutationHandle)
.then(_ => {
return 'New entry was inserted in the MutableData and committed to the network';
});
Free mdHandle
and mutationHandle
from memory
Step 13: Insert reference to service in _public
container
Get _public
container:
let container = '_public';
window.safeApp.getContainer(appHandle, container)
.then((res) => {
mdHandle = res;
return 'Returns handle to Mutable Data behind ' + container + ' container: ' + res;
});
Get mutation object:
window.safeMutableData.newMutation(appHandle)
.then((res) => {
mutationHandle = res;
return 'Returns handle to be able to call safeMutableDataMutation functions: ' + res;
});
Insert reference to service name:
window.safeMutableDataMutation.insert(mutationHandle, '_public/mypublicid/site-root', mdName)
.then(_ => {
return 'Registers an insert operation with mutation handle, later to be applied.';
// You must now run safeMutableData.applyEntriesMutation(mdHandle, mutationHandle) to save changes.
});
The entry key, '_public/mypublicid/site-root'
is our convention for identifying our web service.
The entry value will be the name of our service MD created in Step 4, still held in mdName
variable.
Apply mutation to _public
container:
window.safeMutableData.applyEntriesMutation(mdHandle, mutationHandle)
.then(_ => {
return 'New entry was inserted in the MutableData and committed to the network';
});
Complete.
Reference over view of data structures and what we just created here: https://github.com/hunterlester/safe_dom_api_playground/blob/master/safe_dns.md