-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
GunJS Starterkit and Quickstart
- Developer support
- What is GunJS?
- GunJS tools
- Blueprints
- Quickstart
For GunJS Wiki check https://github.com/amark/gun/wiki
If you have any more questions hit the GunJS community https://gitter.im/amark/gun
In addition, ChatGPT is an excellent tool for crafting well-formulated questions to present to a community of developers. (To get the most out of ChatGPT's language assistance, simply start by saying "improve my text" and provide your initial question or statement. ChatGPT will then generate a response, which you can copy and paste back into the prompt. This process will allow you to refine your original question or statement and make any necessary corrections to ensure that your meaning is clear. By repeating this step, you can achieve a perfectly formulated question or statement that accurately conveys your intended message.)
It makes everything soo much easier. It's perfect for checking ideas right away. See for yourself
Browser: https://openai.com/blog/chatgpt/
Desktop App (Win, Mac, Linux) https://github.com/lencx/ChatGPT (installables on releases page)
NEW! Supertools https://supertools.therundown.ai All available AI Tools on one page (with filter options)
GunJS can be explained as a decentralized alternative to cloud-based services like Dropbox and Firebase. Both Dropbox and Firebase provide cloud-based storage and synchronization of data, but with GunJS, the data is stored and synchronized across a decentralized network of peers, rather than relying on a central server. In this setup, each user's device acts as a node in the network, storing a copy of the data and making it available to other nodes. This means that data is always accessible, even if one node goes offline, because it can be retrieved from another node. GunJS also provides real-time synchronization, ensuring that all nodes in the network have the latest version of the data, similar to Dropbox and Firebase, but with the added benefits of improved security and privacy, as data is not controlled by a central authority. Additionally, GunJS allows for peer-to-peer data sharing, enabling users to directly exchange data without having to go through a central server. This makes it a flexible and scalable solution for real-time data storage and synchronization, similar to Firebase, but with a decentralized approach.
Using public (stranger) relays can pose a risk because they may be wiped at any time. However, this may not necessarily be a significant problem. Instead, developers of gun applications should prioritize the local graph on the client-side, as the relay primarily serves the purpose of syncing, not long-term storage. If the intention is to rely on relays for long-term storage, this technology is not yet mature enough for that purpose. But again, the local graph client-side is your long-term storage. Striking a balance between deduplication, availability, and consistency is crucial.
By the way, have you considered thinking outside the box? For example, for long-term storage, why not encrypt the user profile and store it on Github? A repository could contain all the necessary data, history, pictures, and more. Combined with Github's versioning, the possibilities are endless!
Pubsub is an architecture where you subscribe to a “topic”, like “cats”, then whenever someone publishes a message of topic “cat”, you receive it. A peer-to-peer pubsub network means that anyone can publish, and anyone can subscribe.
A decentralized form handler with Gun for "static" JAMStack websites on a CDN. Plus "Svelte Compiler Tutorial with Gun"
A little taskbar which empowers your fanbase to deploy a Gun relay on multiple ways to support your decentralized website/app.
GunJS - Included and third-party storage solutions https://github.com/amark/gun/wiki/Storage
To permanently store all GUN's data, your GUN server app must define which storage engine to use. By default a GUN server will store the data in a file, but this is just meant to be used during development. It is not a sound solution for production.
However, GUN is different to most other databases when it comes to dev vs prod setup in that the only distinction is that a production setup can maybe include a persistent storage option (e.g. AWS S3, IPFS...) for a participating relay peer. That's not mandatory though, browsers peers with their localStorage storage are sufficient more often than not, doesn't matter if you're still in development or already running your app in production with customers.
https://gist.github.com/pedrouid/b4056fd1f754918ddae86b32cf7d803e (Web Cryptography API Examples - A collection of well commented, well ordered snippets for 20 algorithms)
https://diafygi.github.io/webcrypto-examples/ (This table is live! Every ✓ or ✗ on this page is a test to see if your browser supports that method in WebCryptoAPI.)
TOTP(Time-based one-time password) generator, support for Google Authenticator Demo
-
METAVERSE-DAO Dapp Blueprints
- your Gun App blueprints here...
- GUN Relays
- Basic Principles
- Spaces
- Frozen Space = Content Addressing
- Comparing the graph structure to a folder system
- Fetching and storing data
- SEA (security, encryption, authentication)
- Understanding your GUN Relay
Gun works local first even without ever using a relay, but for syncing between clients you need a relay
.get() | .put | .on - There are more methods...(API) but these three are the basic ones.
This is pretty much the core of everything. Notice how easy it is to connect your front-end code with the graph database GUN.
Notice that no matter how complex your function is: You just drop the result into one or multible variables and connect them to the GUN write function (green boxes, green lines).
Last, you can easily receive the data in any function, again, no matter how complex, by db.on(data => {//your function here});
and get the data that you wrote to GUN before (red boxes, red lines)
This script saves a long/lat pair to the GUN graph, and renders a point on the globe, if the local clients graph (browser local storage) or a connected graph (GUN-Relay) receive a new entry.
The .on(data)
subscribes to the GUN graph. Everything's new to the graph will automatically be rendered on the globe. Both local storage graph and/or relay graph changes!
GUN supports more than just key/value pairs, it is a graph database that can store SQL-like tables, JSON-like documents, files and livestreaming video, plus relational and hypergraph data!
Beware! Anyone can edit the data by default - to fix this, we have to use the User system. GUN is a universal graph which has 3 logical "spaces" protected by SEA:
.get(name).put(data)
https://gun.eco/docs/API
Note: Some data here may be encrypted such that the content stays secret, but it can always be overwritten. Imagine in real life someone hides a prize in a vault at the beach: Once it is found it may be damaged or moved, but only a person who knows the key can unlock it.
user.get(name).put(data)
https://gun.eco/docs/User
(or Key Space) Only data signed with the user's key can be put. Uses SEA. This data can only be be changed, added or removed, by the owner. The data can be either private or publicly readable.
Note: Data is cryptographically owned by the user, there is no "app admin" or "website owner", this may change how you build apps but it guarantees better safety. Owners can authorize or give other users permission to edit the owner's data. Again, the owner does this, not the app developer or database admin.
gun.get('#name').get(name).put(data);
https://gun.eco/docs/Frozen
(Hash Space, Content Id Space) The # operator is used. Gun interprets something like "Only allow data to be put here if its hash matches the appended hash object." This data cannot be changed or removed, only added to. Nobody owns this data.
Note: If nobody stores the data it may be forgotten, if the peers that store it are offline the data may not be found until they are online again. This is true of data in any space though.
When data is added to a hash space, its contents are hashed using a cryptographic algorithm, producing a unique hash value. This hash value is then appended to the location in the content ID space where the data is being added.
Subsequently, if anyone wants to add more data to that same location, they must include the same hash value in their request. This ensures that only data with the correct hash value can be added to that location.
Once data is added to a hash space, it cannot be changed or removed. This means that the data is immutable and its contents remain fixed over time.
Nobody owns the data in a hash space, as it is stored in a decentralized and distributed network. This makes it difficult for any individual or entity to exert control over the data.
Once you know the CID of a file on the network, you have all you need for the network to locate and return the file back to you.
So don't assume data is only in one space! For instance, user data can point to public data which can be edited by anyone, just not the link itself. Likewise, public data could link to frozen or user data, but anyone could edit the link itself. One very powerful combination is frozen links to user data, nobody can edit the link but the data itself can be updated by the owner.
// Logged in user writes a message in his signed graph. Notice, it should be an object in order to have a soul
gun.user().get('messages').set({text:'hello'}).on(async data => { //store message in User Space then...
let soul = data._["#"] // gets us the Soul of the just stored node
let hash = await SEA.work(soul, null, null,{name:'SHA-256'}) // gets us the hash of the above data
gun.get('#messages').get(hash).put(soul) // User puts a hashed soul of the message in a public content-addressed node
})
// Others can read the message later with the soul
gun.get('#messages').map().on(data=> {
gun.get(data).once((d=>console.log(d))) // {text:'hello'}
})
function onSubmit{
// Logged in user stores a post in his signed graph. Notice, it should be an object in order to have a soul
let data = {name: 'Alex', city: 'New York', description: 'blabla', zoomLink: 'https://zoom.us/5sdf4w', long: '50.00', lat: '14.0'}
gun.user(user.is).get('posts').put(data).on(async data => { //store message in User Space then...
let soul = data._["#"] // gets us the Soul of the just stored node
let hash = await SEA.work(soul, null, null,{name:'SHA-256'}) // gets us the hash of the above data
gun.get('#posts').get(hash).put(soul) // User puts a hashed soul of post1 in a public content-addressed node
})}
// Others can read the posts later with the soul
gun.get('#posts').map().on()
Immutable links to mutable user content with SEA.certify
function onSubmit{
// Logged in user(123) stores a post in his signed graph. Notice, it should be an object in order to have a soul
let data = {name: 'Alex', city: 'New York', description: 'blabla', zoomLink: 'https://zoom.us/5sdf4w', long: '50.00', lat: '14.0'}
// user.is and user(123) are the same
gun.user(user.is).get('posts').put(data).on(async data => { //store message in User Space of user(123) then...
let soul = data._["#"] // gets us the Soul of the just stored node
let hash = await SEA.work(soul, null, null,{name:'SHA-256'}) // gets us the hash of the above data
let certificate = await SEA.certify("*", {"*": "interestedpost1", "+": "*"}, user._.sea); // let users write into User Space of the post issuer. (user.is/user(123))
gun.get('#posts').get(hash).put(soul, {opt: {cert: certificate}}) // User puts a hashed soul of post1 in a public content-addressed node
})}
// Others can read the posts later with the soul
gun.get('#posts').map().on()
Now we have a content-addressed post and users will store their interest or participation in the user space of the post issuer/publisher. (user(123))
function onSubmit{
gun.user(123)
.get(interestedpost1).put({pubkey: {user.is}, interested: true/false}, {opt: {cert: certificate}}) //user.is = other user interested in user(123)'s post
}
- objects: partials, circular, and nested
- strings
- numbers
- booleans
- null
Note: Other values, like undefined, NaN, Infinity, arrays, will be rejected. (Saving array in Gun, but rather go for objects from the begin if possible)
gun.put("oops"); // error
gun.get("odd").put("oops"); // error
// Initialize GUN and tell it we will be storing all data local, and sync with relay http://localhost:8765/gun, and under the rootnode 'yourappname' in the graph...
var gun = Gun(['http://localhost:8765/gun']).get('yourappname')
// ...then you can call the same content addresses as above without error
gun.put("oops"); // ok
gun.get("odd").put("oops"); // ok
btw: common is let gun = Gun();
, but no-brainer descriptions are preferable (what was Gun again?) let db = Gun();
and then you can call your database like
db.put("oops"); // ok
db.get("odd").put("oops"); // ok
gun.get('key').put({
property: 'value',
object: {
nested: true
}
})
// strings
gun.get('person').get('name').put('Alice')
// numbers
gun.get('IoT').get('temperature').put(58.6)
// booleans
gun.get('player').get('alive').put(true)
.get(name).get(name).on(data) // subscribes to ```.get(name).get(name)``` in public space
user.get(name).get(name).on(data) //subscribes to ```.get(name).get(name)``` in user space
.get(name).get(name).once(data) // fetches ```.get(name).get(name)``` in public space once
user.get(name).get(name).once(data) // fetches ```.get(name).get(name)``` in user space once
Note: GUN is a functional reactive database for streaming event-driven data, gotta love/hate buzzwords - right? This means that .on subscribes to realtime updates, and may get called many times. Meanwhile .once grabs the data once, which is useful for procedural operations.
.get(name).get(name).put(data) // store data in ```.get(name).get(name)``` in public space
user.get(name).get(name).put(data) //store data in ```.get(name).get(name)``` in user space
- When a user signs in or creates an account, their username and password are stored in the GUN database for recall.
- The Passphrase the user chooses will be extended with PBKDF2 to make it a secure way to login.
- When a user logs out, their session is terminated.
This code encrypts the text 'secret text' with the user's public key using the Gun.SEA.encrypt method. The encrypted text is then stored in the user's private space using the db.user().get('encryptedText').put(encryptedText) method. NOTE: We need an authenticated (logged-in) user for this code to work.
ok so this sounds like a very complex setup to be decentralized and I dont want to have to pay for a bunch of relays I own myself. I highly doubt any of my customers would "donate" a relay. Wouldnt that mean it has to be always on? Or on a decent amount of the time? And all this wiping a relay and concern about security and encryption....makes me think GUN is not ready for production use
I am actually about to go into my first production with my first Gun dapp, Couchsurfing Decentralized. Should be ready by the middle of the next week.
But full control, flexibility, speed(at least Gun), privacy, and endless potential because of the new possible design approachs.
😂 I hear that often first, but of course you need an incentive to activate your customer/user to donate a relay on, for instance, their desktop. A value could be a faster access due to your user hosting a relay for instance.
Gun is a local first database which works out of the box even without a relay. Then just limited to its local graph and does not sync.
For your understanding: you could have a local app which's database is Gun and which runs years local without even ever connecting to a relay.
By experience a relay runs days, weeks, rather month, before it gets wiped, but I estimate the median lifetime of a relay to be a few hours to 48hours max. (just for my worst case calculations!)
In this time(few hours to 48h) we can sync the local graphs(databases) of our clients with no problem.
If the origin client of a piece of data is offline, the relays will continue to distribute the synced piece of data.
And there is a high chance the user comes back within 48 hours, making the piece of data available to the relays again.
After, the clients and the relays will start it's operation like nothing had happened before. So the relays start again to sync the local graphs of the clients to each other, based on the clients subscription/content-address gun.get(something).get(something).on(data)
you say they have a lifetime of 48 hours max....how does that work? Does the relay server actually shut down?
And its more about a reset which could cause a wipe, or a wipe causing a reset. Its not so much about relays would shut down(except in the following free tier example), they are stable, they run and they reset if necessary.
- If its a free tier relay, with limitations, then it has maybe not 24h uptime, or gets wiped every x hours/days. (have minimum 3 of them at different free providers so they will balance each other out)
- You have no control over relays that you dont own. They could be wiped any time. (have minimum 3 foreign relays so they will balance each other out)
- Incentivised or "donated" desktop relays go on and off, new come, old go. (have minimum 3 donated relays so they will balance each other out.
Makes 9 individual "dangerous" relays, but if we would go for the following code, which combines all 9 of them, i promise you no problems at all. Thats decentralization at its best...
// this code says all your Gun app client users initialize their Gun instance with all 9 relays at once.
var db = Gun(['https://relay1.com/gun, https://relay2.com/gun, https://relay3.com/gun, https://relay4.com/gun, https://relay5.com/gun, https://relay6.com/gun, https://relay7.com/gun, https://relay8.com/gun, https://relay9.com/gun,'])
Incentivised or "donated" desktop relays go on and off, new come, old go. (have minimum 3 donated relays so they will balance each other out.
You could go for a script which fills the {drelays} variable at the begin of the following code from a list of donated relays. The criteria to add and delete relays from that list could be their monthly uptime.
var drelays = relayUptimeFilter();
var db = Gun(['{drelays}, https://relay1.com/gun, https://relay2.com/gun, https://relay3.com/gun, https://relay4.com/gun, https://relay5.com/gun, https://relay6.com/gun'])