summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--graffiti.js180
-rw-r--r--test.html73
2 files changed, 140 insertions, 113 deletions
diff --git a/graffiti.js b/graffiti.js
index fe7c2d8..f0a087e 100644
--- a/graffiti.js
+++ b/graffiti.js
@@ -2,11 +2,16 @@ import Auth from './auth.js'
export default class {
- constructor(graffitiURL="https://graffiti.garden") {
+ // There needs to be a new object map for each tag
+ constructor(
+ graffitiURL="https://graffiti.garden",
+ objectMapConstructor=()=>({})) {
+
this.graffitiURL = graffitiURL
+ this.objectMapConstructor = objectMapConstructor
this.open = false
- this.subscriptionData = {}
this.eventTarget = new EventTarget()
+ this.tagMap = {}
}
// CALL THIS BEFORE DOING ANYTHING ELSE
@@ -26,8 +31,13 @@ export default class {
this.wsURL.searchParams.set("token", this.authParams.token)
}
- // And commence connection
+ // Commence connection
this.connect()
+
+ // Wait until open
+ await new Promise(resolve => {
+ this.eventTarget.addEventListener("graffitiOpen", () => resolve() )
+ })
}
connect() {
@@ -51,6 +61,10 @@ export default class {
}
async request(msg) {
+ if (!this.open) {
+ throw { 'error': 'Not connected!' }
+ }
+
// Create a random message ID
const messageID = crypto.randomUUID()
@@ -61,13 +75,6 @@ export default class {
})
})
- // Wait for the socket to open
- if (!this.open) {
- await new Promise(resolve => {
- this.eventTarget.addEventListener("graffitiOpen", () => resolve() )
- })
- }
-
// Send the request
msg.messageID = messageID
this.ws.send(JSON.stringify(msg))
@@ -79,7 +86,7 @@ export default class {
if (data.type == 'error') {
throw data
} else {
- return data
+ return data['reply']
}
}
@@ -93,30 +100,23 @@ export default class {
messageEvent.data = data
this.eventTarget.dispatchEvent(messageEvent)
- } else if (['updates', 'removes'].includes(data.type)) {
- // Subscription data
- if (data.queryID in this.subscriptionData) {
- const sd = this.subscriptionData[data.queryID]
+ } else if ('update' in data || 'remove' in data) {
- // For each data point, either add or remove it
- for (const r of data.results) {
- if (data.type == 'updates') {
- sd.updateCallback(r)
- } else {
- sd.removeCallback(r)
- }
- }
+ const object = 'update' in data? data['update'] : data['remove']
+ const uuid = this.objectUUID(object)
- // And update this query's notion of "now"
- if (data.complete) {
- if (data.historical) {
- sd.historyComplete = true
- }
- if (sd.historyComplete) {
- sd.since = data.now
+ for (const tag of object._tags) {
+ if (tag in this.tagMap) {
+ const om = this.tagMap[tag].objectMap
+
+ if ('remove' in data) {
+ delete om[uuid]
+ } else {
+ om[uuid] = object
}
}
}
+
} else if (data.type == 'error') {
if (data.reason == 'authorization') {
Auth.logOut()
@@ -125,61 +125,98 @@ export default class {
}
}
- async update(object, query) {
- const data = await this.request({ object, query })
- return data.objectID
+ async update(object) {
+ // TODO
+ // Add the logic in vue to here
+ return await this.request({ update: object })
}
- async remove(objectID) {
- await this.request({ objectID })
+ async remove(objectKey) {
+ // TODO
+ // same
+ return await this.request({ remove: objectKey })
}
- async subscribe(
- query,
- updateCallback,
- removeCallback,
- flags={},
- since=null,
- queryID=null) {
+ async lsTags() {
+ return await this.request({ ls: null })
+ }
- // Create a random query ID
- if (!queryID) queryID = crypto.randomUUID()
+ async objectByKey(userID, objectKey) {
+ return await this.request({ get: {
+ _by: userID,
+ _key: objectKey
+ }})
+ }
- // Send the request
- await this.request({ queryID, query, since, ...flags })
+ objectsByTags(...tags) {
+ for (const tag of tags) {
+ if (!(tag in this.tagMap)) {
+ throw `You are not subscribed to '${tag}'`
+ }
+ }
- // Store the subscription in case of disconnections
- this.subscriptionData[queryID] = {
- query, since, flags, updateCallback, removeCallback,
- historyComplete: false
+ // Merge by UUID to combine all the maps
+ const combinedMaps = Object.assign({},
+ ...tags.map(tag=> this.tagMap[tag].objectMap))
+
+ // Return just the array
+ return Object.values(combinedMaps)
+ }
+
+ async subscribe(...tags) {
+ // Look at what is already subscribed to
+ const subscribingTags = []
+ for (const tag of tags) {
+ if (tag in this.tagMap) {
+ // Increase the count
+ this.tagMap[tag].count++
+ } else {
+ // Create a new slot
+ this.tagMap[tag] = {
+ objectMap: this.objectMapConstructor(),
+ count: 1
+ }
+ subscribingTags.push(tag)
+ }
}
- return queryID
+ // Begin subscribing in the background
+ if (subscribingTags.length)
+ await this.request({ subscribe: subscribingTags })
}
- async unsubscribe(queryID) {
- // Remove allocated space
- delete this.subscriptionData[queryID]
+ async unsubscribe(...tags) {
+ // Decrease the count of each tag,
+ // removing and marking if necessary
+ const unsubscribingTags = []
+ for (const tag of tags) {
+ this.tagMap[tag].count--
+
+ if (!this.tagMap[tag].count) {
+ unsubscribingTags.push(tag)
+ delete this.tagMap[tag]
+ }
+ }
- // And unsubscribe
- const data = await this.request({ queryID })
+ // Unsubscribe from all remaining tags
+ if (unsubscribingTags.length)
+ await this.request({ unsubscribe: unsubscribingTags })
}
async onOpen() {
console.log("connected to the graffiti socket")
this.open = true
this.eventTarget.dispatchEvent(new Event("graffitiOpen"))
- // Resubscribe to hanging queries
- for (const queryID in this.subscriptionData) {
- const sd = this.subscriptionData[queryID]
- await this.subscribe(
- sd.query,
- sd.updateCallback,
- sd.removeCallback,
- sd.flags,
- sd.since,
- queryID)
+
+ // Clear data
+ for (let tag in this.tagMap) {
+ const objectMap = this.tagMap[tag].objectMap
+ for (let uuid in objectMap) delete objectMap[uuid]
}
+
+ // Resubscribe
+ const tags = Object.keys(this.tagMap)
+ if (tags.length) await this.request({ subscribe: tags })
}
// Adds required fields to an object.
@@ -192,20 +229,21 @@ export default class {
}
// Pre-generate the object's ID if it does not already exist
- if (!object._id) object._id = crypto.randomUUID()
+ if (!object._key) object._key = crypto.randomUUID()
+
+ return object
}
// Utility function to get a universally unique string
// that represents a particular object
objectUUID(object) {
- if (!object._id || !object._by) {
+ if (!object._by || !object._key) {
throw {
type: 'error',
- content: 'the object you are trying to identify does not have an ID or owner',
+ content: 'the object you are trying to identify does not have an owner or key',
object
}
}
- return object._id + object._by
+ return object._by + object._key
}
-
}
diff --git a/test.html b/test.html
index 3a0d853..3623578 100644
--- a/test.html
+++ b/test.html
@@ -5,7 +5,7 @@
<p id="ID"></p>
- <h2 id="status">Unsubscribed</h2>
+ <h2 id="status"></h2>
<button onclick="Subscribe()">
Subscribe
@@ -31,7 +31,7 @@
import Graffiti from "./graffiti.js"
// Connect to a local Graffiti instance
- // (see the server README for how to run locally)
+ // (see the server README for how to n locally)
const graffiti = new Graffiti("http://localhost:5001")
await graffiti.initialize()
@@ -41,57 +41,46 @@
window.LogOut = ()=> graffiti.toggleLogIn()
document.getElementById('ID').innerHTML = `Your Graffiti ID is: ${graffiti.myID}`
- // Create a display counter
- let count = 0
- function displayCount() {
- document.getElementById('status').innerHTML = `Subscribed: ${count} Objects`
+ const myTag = "asdf"
+
+ // Make a display
+ async function displayObjects() {
+ await new Promise(r => setTimeout(r, 1000));
+
+ let display = 'not subscribed'
+ try {
+ const objects = graffiti.objectsByTags(myTag)
+ display = `objects: ${JSON.stringify(objects)}`
+ } catch {}
+
+ document.getElementById('status').innerHTML = display
+ }
+
+ // Create an object containing a special string
+ window.Subscribe = async function() {
+ await graffiti.subscribe(myTag)
+ displayObjects()
}
- // From here to below we're going to
- // define functions that can be activated
- // with button presses, corresponding to
- // each of the four Graffiti primitives.
+ window.Unsubscribe = async function() {
+ await graffiti.unsubscribe(myTag)
+ displayObjects()
+ }
// Create an object containing a special string
- const special = crypto.randomUUID()
- const usedIDs = []
window.Update = async function() {
- usedIDs.unshift(crypto.randomUUID())
- await graffiti.update({
- _id: usedIDs[0],
- _by: graffiti.myID,
- special
- }, {})
+ console.log(await graffiti.update(
+ graffiti.completeObject({_tags: [myTag]})))
+ displayObjects()
}
// Remove an existing object
window.Remove = async function() {
- if ( usedIDs.length ) {
- await graffiti.remove( usedIDs.pop() )
- }
+ const objects = graffiti.objectsByTags(myTag)
+ console.log(await graffiti.remove(objects[0]._key))
+ displayObjects()
}
- // Subscribe to objects containing the special string
- let queryID = null
- window.Subscribe = async function() {
- if (queryID) return
- count = 0
- queryID = await graffiti.subscribe(
- { special },
- (obj)=> { count++; displayCount() },
- (obj)=> { count--; displayCount() }
- )
- displayCount()
- }
-
- // Unsubscribe to the existing query
- window.Unsubscribe = async function() {
- if (queryID) {
- await graffiti.unsubscribe(queryID)
- queryID = null
- document.getElementById('status').innerHTML = "Unsubscribed"
- }
- }
</script>
</body>
</html>