Sufficiently complete for basic use of the API. Needs additions for

multiple collaborators and some other things, which will be added in
the coming months.
This commit is contained in:
Dustin Wehr
2015-06-06 23:35:38 -04:00
parent 8c7444882a
commit c4d7013d2c
2 changed files with 717 additions and 0 deletions

521
google-realtime/google-realtime.d.ts vendored Normal file
View File

@@ -0,0 +1,521 @@
// Type definitions for Google Realtime API
// Project: https://developers.google.com/google-apps/realtime/
// Definitions by: Dustin Wehr <http://cs.toronto.edu/~wehr>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
// For Typescript newbs: To get shorter names, use e.g.
// type CollabModel = googleRealtime.Model;
// interface CollabList<T> extends googleRealtime.CollaborativeList<T> {}
// See section "Type Aliases" of http://www.typescriptlang.org/Content/TypeScript%20Language%20Specification.pdf
// Note the occurrences of "INCOMPLETE". For some interfaces and object types, I have only included
// the properties and methods that I've actually used so-far, and will add more as they become useful to me.
// Or, maybe you want to complete them?
declare module googleRealtime {
type GoogEventHandler = ((evt:ObjectChangedEvent) => void) | ((e:Event) => void) | EventListener;
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeObject
export class CollaborativeObject {
// The id of this collaborative object. Read-only.
id:string;
// The type of this collaborative object. For standard collaborative objects,
// see gapi.drive.realtime.CollaborrativeType for possible values; for custom collaborative objects, this value is
// application-defined.
// Addition: the possible values for standard objects are EditableString, List, and Map.
type:string;
// Adds an event listener to the event target. The same handler can only be added once per the type.
// Even if you add the same handler multiple times using the same type then it will only be called once
// when the event is dispatched.
addEventListener(type:string, listener: GoogEventHandler, opt_capture?:boolean):void;
// Removes all event listeners from this object.
removeAllEventListeners():void;
// Removes an event listener from the event target. The handler must be the same object as the one added.
// If the handler has not been added then nothing is done.
removeEventListener(type:string, listener: GoogEventHandler, opt_capture?:boolean):void;
// Returns a string representation of this collaborative object.
toString():string;
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.IndexReference
export class IndexReference<V> extends CollaborativeObject {
// (Categories of) the shift behavior of an index reference when the element it points at is deleted.
static DeleteMode:{
SHIFT_AFTER_DELETE: string
SHIFT_BEFORE_DELETE: string
SHIFT_TO_INVALID: string
};
//The index of the current location the reference points to. Write to this property to change the referenced index.
index:number;
// The behavior of this index reference when the element it points at is deleted.
// @return one of the elements of DeleteMode
deleteMode():string;
// The object this reference points to. Read-only.
referencedObject():V;
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeMap
export class CollaborativeMap<V> extends CollaborativeObject {
size:string;
static type:string; // equals "Map"
// Removes all entries.
clear():void;
// Removes the entry for the given key (if such an entry exists).
// @return the value that was mapped to this key, or null if there was no existing value.
delete(key:string):V;
// Returns the value mapped to the given key.
get(key:string):V;
// Checks if this map contains an entry for the given key.
has(key:string):boolean;
// Returns whether this map is empty.
isEmpty():boolean;
// Returns an array containing a copy of the items in this map. Modifications to the returned array do
// not modify this collaborative map.
// @return non-null Array of Arrays, where the inner arrays are tupples [string, V]
items():[string,V][];
// Returns an array containing a copy of the keys in this map. Modifications to the returned array
// do not modify this collaborative map.
keys():string[];
// Put the value into the map with the given key, overwriting an existing value for that key.
// @return the old map value, if any, that used to be mapped to the given key.
set(key:string, value:V):V;
// Returns an array containing a copy of the values in this map. Modifications to the returned array
// do not modify this collaborative map.
values():V[];
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeString
export class CollaborativeString extends CollaborativeObject {
// The length of the string. Read only.
length:number;
// The text of this collaborative string. Reading from this property is equivalent to calling getText(). Writing to this property is equivalent to calling setText().
text:string;
static type:string; // equals "EditableString"
// Appends a string to the end of this one.
append(text:string):void;
// Gets a string representation of the collaborative string.
getText():string;
// Inserts a string into the collaborative string at a specific index.
insertString(index:number, text:string):void;
// Creates an IndexReference at the given {@code index}. If {@code canBeDeleted} is set, then a delete
// over the index will delete the reference. Otherwise the reference will shift to the beginning of the deleted range.
registerReference(index:number, canBeDeleted:boolean):IndexReference<CollaborativeString>;
// Deletes the text between startIndex (inclusive) and endIndex (exclusive).
removeRange(startIndex:number, endIndex:number):void;
// Sets the contents of this collaborative string. Note that this method performs a text diff between the
// current string contents and the new contents so that the string will be modified using the minimum number
// of text inserts and deletes possible to change the current contents to the newly-specified contents.
setText(text:string):void;
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.CollaborativeList
export class CollaborativeList<V> extends CollaborativeObject {
// The number of entries in the list. Assign to this field to reduce the size of the list.
// Note that the length given must be less than or equal to the current size.
// The length of a list cannot be extended in this way.
length:number;
static type:string; // equals "List"
// Returns a copy of the contents of this collaborative list as an array.
// Changes to the returned object will not affect the original collaborative list.
asArray():V[];
// Removes all values from the list.
clear():void;
// Gets the value at the given index.
get(ind:number):V;
//Returns the first index of the given value, or -1 if it cannot be found.
indexOf(value:V, opt_comparatorFn?:(x1:V, x2:V) => boolean):number;
//Inserts an item into the list at a given index.
insert(index:number, value:V):void;
// Inserts a list of items into the list at a given index.
insertAll(index:number, values:V[]):void;
// Returns the last index of the given value, or -1 if it cannot be found.
lastIndexOf(value:V, opt_comparatorFn?:(x1:V, x2:V) => boolean):number;
//Moves a single element in this list (at index) to immediately before destinationIndex.
//Both indices are with respect to the position of elements before the move.
//For example, given the list: ['A', 'B', 'C']
//move(0, 0) is a no-op
//move(0, 1) is a no-op
//move(0, 2) yields ['B', 'A', 'C'] ('A' is moved to immediately before 'C')
//move(0, 3) yields ['B', 'C', 'A'] ('A' is moved to immediately before an imaginary element after the list end)
//move(1, 0) yields ['B', 'A', 'C'] ('B' is moved to immediately before 'A')
//move(1, 1) is a no-op
//move(1, 2) is a no-op
//move(1, 3) yields ['A', 'C', 'B'] ('B' is moved to immediately before an imaginary element after the list end)
move(index:number, destinationIndex:number):void;
// Moves a single element in this list (at index) to immediately before destinationIndex in the list destination.
// Both indices are with respect to the position of elements before the move.
// If the provided destination is this list, this function is identical to move(index, destinationIndex).
moveToList(index:number, destination:CollaborativeList<V>, destinationIndex:number):void;
// Adds an item to the end of the list.
// @return the new length of the list
push(value:V):number;
// Adds an array of values to the end of the list.
pushAll(values:V[]):void;
// Creates an IndexReference at the given index. If canBeDeleted is true, then a delete over the index will delete
// the reference. Otherwise the reference will shift to the beginning of the deleted range.
registerReference(index:number, canBeDeleted:boolean):IndexReference<CollaborativeList<V>>;
// Removes the item at the given index from the list.
remove(index:number):void;
// Removes the items between startIndex (inclusive) and endIndex (exclusive).
removeRange(startIndex:number, endIndex:number):void;
// Removes the first instance of the given value from the list.
// @return whether the item was removed
removeValue(value:V):boolean;
// Replaces items in the list with the given items, starting at the given index.
replaceRange(index:number, values:V[]):void;
// Sets the item at the given index
set(index:number, value:V):void;
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.Model
export class Model {
// Returns the collaborative object with the given id.
// @return non-null Object
getObject:any;
// An estimate of the number of bytes used by data stored in the model.
bytesUsed:number;
// True if the model can currently redo.
canRedo:boolean;
// True if the model can currently undo.
canUndo:boolean;
// Creates the native JS object for a given collaborative object type.
// @return non-null Object
createJsObject(typeName:string):any;
// Adds an event listener to the event target.
// The same handler can only be added once per the type. Even if you add the same handler multiple times using the
// same type then it will only be called once when the event is dispatched.
addEventListener(type:string, listener:() => void | EventListener, opt_capture?:boolean):void;
// Starts a compound operation. If a name is given, that name will be recorded in the mutation for use in revision
// history, undo menus, etc. When beginCompoundOperation() is called, all subsequent edits to the data model will
// be batched together in the undo stack and revision history until endCompoundOperation() is called.
// Compound operations may be nested inside other compound operations.
// If the root compound operation is undoable, all nested compound operations must be undoable as well.
// If the root compound operation is non-undoable, nested operations can be undoable, although the entire operation
// will obey the root's opt_isUndoable value.
// Note that the compound operation MUST start and end in the same synchronous execution block. If this invariant
// is violated, the data model will become invalid and all future changes will fail.
beginCompoundOperation(opt_name?:string, opt_isUndoable?:boolean):void;
// Creates and returns a new collaborative object. This can be used to create custom collaborative objects.
// For built in types, use the specific create* functions.
// @return non-null Object
create(ref:string|Function, ...var_args:any[]):any;
// Creates a collaborative list.
createList<T>(opt_initialValue?:Array<T>):CollaborativeList<T>;
// Creates a collaborative map.
createMap<T>(opt_initialValue?:Array<[string,T]>):CollaborativeMap<T>;
// Creates a collaborative string.
createString(opt_initialValue?:string):CollaborativeString;
//Ends a compound operation. This method will throw an exception if no compound operation is in progress.
endCompoundOperation():void;
// Returns the root of the object model.
getRoot():CollaborativeMap<any>;
// The mode of the document. If true, the document is read-only. If false, it is editable.
isReadOnly():boolean;
// Redo the last thing the active collaborator undid.
redo():void;
// Removes all event listeners from this object.
removeAllEventListeners():void;
// Removes an event listener from the event target. The handler must be the same object as the one added.
// If the handler has not been added then nothing is done.
removeEventListener(type:string, listener:() => void | EventListener, opt_capture?:boolean):void;
// The current server revision number for this model. The revision number begins at 1 (the initial empty model)
// and is incremented each time the model is changed on the server (either by the current session or any
// other collaborator). Because this revision number includes only changes that the server knows about,
// it is only updated while this client is connected to the Realtime API server and it does not include changes
// that have not yet been saved to the server.
serverRevision():number;
// Serializes this data model to a JSON-based format which is compatible with the Realtime API's import/export
// REST API. The exported JSON can also be used with gapi.drive.realtime.loadFromJson to load an in-memory
// version of this data model which does not require a network connection.
// See https://developers.google.com/drive/v2/reference/realtime/update for more information.
toJson(opt_appId?:string, opt_revision?:number):string;
// Undo the last thing the active collaborator did.
undo():void;
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.BaseModelEvent
interface BaseModelEvent {
// Whether this event bubbles.
bubbles : boolean;
// The list of names from the hierarchy of compound operations that initiated this event.
compoundOperationNames : string[];
// True if this event originated in the local session.
isLocal : boolean;
// True if this event originated from a redo call.
isRedo : boolean;
// True if this event originated from an undo call.
isUndo : boolean;
// Prevents an event from performing its default action. In the Realtime API, this function is only present
// for compatibility with the DOM event interface and therefore it does nothing.
preventDefault() : void;
// The id of the session that initiated this event.
sessionId : string;
// The collaborative object that initiated this event.
target : Object;
// The type of the event.
type : string;
// The user id of the user that initiated this event.
userId : string;
// Stops an event which bubbles from propagating to the target's parent.
stopPropagation() : void;
/* Parameters:
target
gapi.drive.realtime.CollaborativeObject
The collaborative object that initiated the event.
Value must not be null.
sessionId
string
The id of the session that initiated the event.
userId
string
The user id of the user that initiated the event.
compoundOperationNames
Array of string
The list of names from the hierarchy of compound operations that initiated the event.
Value must not be null.
isLocal
boolean
True if the event originated in the local session.
isUndo
boolean
True if the event originated from an undo call.
isRedo
boolean
True if the event originated from a redo call.
*/
new (target:CollaborativeObject, sessionId:string, userId:string, compoundOperationNames: string[],
isLocal:boolean, isUndo:boolean, isRedo:boolean) : BaseModelEvent;
}
// Complete
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.ObjectChangedEvent
interface ObjectChangedEvent extends BaseModelEvent {
// parameters as in BaseModelEvent above except for addition of:
// events:
// Array of gapi.drive.realtime.BaseModelEvent
// The specific events that document the changes that occurred on the object.
// Value must not be null.
new (target:CollaborativeObject, sessionId:string, userId:string, compoundOperationNames: string[],
isLocal:boolean, isUndo:boolean, isRedo:boolean, events:BaseModelEvent[]) : ObjectChangedEvent;
// The specific events that document the changes that occurred on the object.
events : BaseModelEvent[];
}
// INCOMPLETE
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.Document
export class Document {
// Gets the collaborative model associated with this document.
// @return non-null Model
getModel():Model;
// Closes the document and disconnects from the server.
// After this function is called, event listeners will no longer fire and attempts to access the document, model,
// or model objects will throw a gapi.drive.realtime.DocumentClosedError.
// Calling this function after the document has been closed will have no effect.
close():void;
}
// ***********************************
// The remainder of this file types some (not all) things in realtime-client-utils.js, found here:
// https://developers.google.com/google-apps/realtime/realtime-quickstart
// and
// https://apis.google.com/js/api.js
// ***********************************
// Complete
export interface LoaderOptions {
// Your Application ID from the Google APIs Console.
appId: string;
// Autocreate files right after auth automatically.
autoCreate: boolean;
// Client ID from the console.
clientId: string;
// The ID of the button to click to authorize. Must be a DOM element ID.
authButtonElementId: string;
// The MIME type of newly created Drive Files. By default the application
// specific MIME type will be used:
// application/vnd.google-apps.drive-sdk.
newFileMimeType: string;
//newFileMimeType = null // default
// Function to be called to initialize custom Collaborative Objects types.
registerTypes: () => void;
// The name of newly created Drive files, if no title is specified.
defaultTitle: string;
// Function to be called after authorization and before loading files.
afterAuth: () => void;
// Function to be called when a Realtime model is first created.
initializeModel: (model:Model) => void;
// Function to be called every time a Realtime file is loaded.
onFileLoaded: (rtdoc:Document) => void;
}
// INCOMPLETE
export interface DriveAPIFileResource {
id: string;
}
// INCOMPLETE
export interface RealtimeLoader {
start():void;
load():void;
}
interface RealtimeLoaderFactory {
new (options:googleRealtime.LoaderOptions) : RealtimeLoader;
}
// INCOMPLETE
export interface ClientUtils {
// INCOMPLETE
params: {
// string containing one or more file ids separated by spaces.
fileIds : string
};
RealtimeLoader : RealtimeLoaderFactory;
/**
* Creates a new Realtime file.
* @param title {string} title of the newly created file.
* @param mimeType {string} the MIME type of the new file.
* @param callback {(file:DriveAPIFileResource) => void} the callback to call after creation.
*/
createRealtimeFile(title:string, mimeType:string, callback:(file:DriveAPIFileResource) => void) : void;
}
// COMPLETE
// https://developers.google.com/google-apps/realtime/reference/gapi.drive.realtime.databinding.Binding
export interface Binding {
// Throws gapi.drive.realtime.databinding.AlreadyBoundError If domElement has already been bound.
// The collaborative object to bind.
collaborativeObject : CollaborativeObject;
// The DOM element that the collaborative object is bound to. Value must not be null.
domElement : Element;
// Unbinds the domElement from collaborativeObject.
unbind() : void;
}
export interface GoogleAPI {
drive : {
realtime : {
databinding : {
bindString(s:googleRealtime.CollaborativeString, textinput:HTMLInputElement) : googleRealtime.Binding;
}
EventType : {
TEXT_INSERTED: string;
TEXT_DELETED: string;
OBJECT_CHANGED: string;
}
}
}
}
}
// global var introduced by realtime-client-utils.js
declare var rtclient:googleRealtime.ClientUtils;
// global var introduced by https://apis.google.com/js/api.js
declare var gapi: googleRealtime.GoogleAPI;

View File

@@ -0,0 +1,196 @@
/// <reference path="google-realtime.d.ts" />
// Don't use this as a reference. Use the examples at
// https://developers.google.com/google-apps/realtime/
// To use the Realtime API effectively, I needed to read lots of the
// (well-written) documentation on the site, and to understand parts of
// realtime-client-utils.js
// which you can find in the tutorial section of the project's homepage.
declare var $ : any;
interface JQuery {
[key: string]: any;
};
type CollabModel = googleRealtime.Model;
type CollabDoc = googleRealtime.Document;
interface CollaborativeObject extends googleRealtime.CollaborativeObject {}
interface CollaborativeList<T> extends googleRealtime.CollaborativeList<T> {}
interface CollaborativeMap<T> extends googleRealtime.CollaborativeMap<T> {}
interface IndexReference<T> extends googleRealtime.IndexReference<T> {}
interface CollaborativeString extends googleRealtime.CollaborativeString {}
type CListOfCObj = CollaborativeList<CollaborativeObject>
type CObjOrStr = CollaborativeObject | string;
type CMapOfCObjOrStr = CollaborativeMap<CObjOrStr>;
module GRealtime {
var default_loader_options : googleRealtime.LoaderOptions = {
// Your Application ID from the Google APIs Console.
appId: "YOUR_APP_ID",
// This tells us if need to we automatically create a file after auth.
autoCreate: false,
// Client ID from the console.
clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
// The ID of the button to click to authorize. Must be a DOM element ID.
authButtonElementId: 'realtime-authorize-button',
// The MIME type of newly created Drive Files. By default the application
// specific MIME type will be used:
// application/vnd.google-apps.drive-sdk.
//newFileMimeType: 'text/json',
newFileMimeType: 'text',
//newFileMimeType: null, // default
// Function to be called to initialize custom Collaborative Objects types.
registerTypes: null, // No action
defaultTitle: "Default default-doc-title",
// The rest are only defaults
afterAuth: function() : void {
console.log("default afterAuth called")
},
initializeModel: function(rtmodel:CollabModel) : void {
console.log("default initializeModel called");
},
onFileLoaded : function(rtdoc:CollabDoc) : void {
console.log("default onFileLoaded called");
}
};
export class MyRTLoader {
public loader_options : googleRealtime.LoaderOptions = $.extend({},default_loader_options);
private rtloader_client : googleRealtime.RealtimeLoader;
// call after setting loader_options appropriately
authorize() {
this.rtloader_client = new rtclient.RealtimeLoader(this.loader_options);
this.rtloader_client.start();
}
createNew(title:string, callback: (file:any) => void) {
rtclient.createRealtimeFile(title, null, callback);
}
loadAfterAuth(fileid:string) {
// use this as part of your afterAuth callback
rtclient.params.fileIds = fileid;
this.rtloader_client.load();
}
}
export class MyRealtimeDoc {
protected rtmodel: CollabModel;
protected rtdoc: CollabDoc;
private myRTLoader = new GRealtime.MyRTLoader();
newFile(title: string,
initializeModel: (x:CollabModel) => void,
onFileLoaded: (x:CollabDoc) => void) : void {
var _afterAuth = () => {
this.myRTLoader.createNew(title, (file:googleRealtime.DriveAPIFileResource) => {
console.log(`\n\nThis is the createNew callback. New file's id: ${file.id}\n\n`);
$("#file-id-text-input").val(file.id);
this.myRTLoader.loadAfterAuth(file.id)
})
}
var _initializeModel = (model:CollabModel) => {
console.log("\n\nRTModel initialized for NEW document.\n\n");
this.rtmodel = model;
if( initializeModel ) {
initializeModel(model);
}
}
var _onFileLoaded = (doc:CollabDoc) => {
console.log("\n\nNEW document loaded.\n\n");
this.rtmodel = doc.getModel();
this.rtdoc = doc;
if( onFileLoaded ) {
onFileLoaded(doc);
}
}
this.myRTLoader.loader_options.onFileLoaded = _onFileLoaded;
this.myRTLoader.loader_options.afterAuth = _afterAuth;
this.myRTLoader.loader_options.initializeModel = _initializeModel;
this.myRTLoader.authorize();
}
loadExisting(fileid: string,
onFileLoaded: (doc:CollabDoc) => void) : void {
rtclient.params.fileIds = fileid;
var _onFileLoaded = (doc:CollabDoc) => {
console.log("\n\nEXISTING document loaded.\n\n");
this.rtdoc = doc;
this.rtmodel = doc.getModel();
if( onFileLoaded ) {
onFileLoaded(doc);
}
};
this.myRTLoader.loader_options.onFileLoaded = _onFileLoaded;
//this.myRTLoader.loader_options.afterAuth = ...
this.myRTLoader.authorize();
}
createString() : CollaborativeString { return this.rtmodel.createString(""); }
createList() : CollaborativeList<CollaborativeObject> { return this.rtmodel.createList<CollaborativeObject>(); }
createMap() : CollaborativeMap<CollaborativeObject> { return this.rtmodel.createMap<CollaborativeObject>(); }
addToPersistDocRoot(x:{pdata:any}, key:string) {
this.rtmodel.getRoot().set(key,x.pdata);
}
bindString(istring:CollaborativeString, $textinput: JQuery) : googleRealtime.Binding {
return gapi.drive.realtime.databinding.bindString(
<CollaborativeString>istring,
<HTMLInputElement>$textinput[0] );
}
}
// alternative to RealtimePSDoc.bindString
function registerLocalStringChangeListener(
x: CollaborativeString,
listener_or_callback: (e:Event) => void | EventListener) : void {
x.addEventListener(gapi.drive.realtime.EventType.TEXT_INSERTED, listener_or_callback);
x.addEventListener(gapi.drive.realtime.EventType.TEXT_DELETED, listener_or_callback);
}
}
// Next example from https://developers.google.com/google-apps/realtime/model-events
declare var doc : CollabDoc;
function displayObjectChangedEvent(evt:googleRealtime.ObjectChangedEvent) {
var events = evt.events;
var eventCount = evt.events.length;
for (var i = 0; i < eventCount; i++) {
console.log('Event type: ' + events[i].type);
console.log('Local event: ' + events[i].isLocal);
console.log('User ID: ' + events[i].userId);
console.log('Session ID: ' + events[i].sessionId);
}
}
doc.getModel().getRoot().addEventListener(gapi.drive.realtime.EventType.OBJECT_CHANGED, displayObjectChangedEvent);