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
_publicNamescontainer and insert encrypted entry for key is the encryptedmypublicidand the value is the encrypted hash ofmypublicid - Get
_publiccontainer and insert entry where key is, in this case,_public/mypublicid/site-rootand 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.mypublicidin 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