Repository: rlamasb/Firebase.Xamarin Branch: master Commit: 1fbd4149a048 Files: 47 Total size: 118.9 KB Directory structure: gitextract_p1_b5ys5/ ├── .gitignore ├── Auth/ │ ├── FirebaseAuth.cs │ ├── FirebaseAuthException.cs │ ├── FirebaseAuthLink.cs │ ├── FirebaseAuthProvider.cs │ ├── FirebaseAuthType.cs │ ├── FirebaseConfig.cs │ ├── IFirebaseAuthProvider.cs │ └── User.cs ├── Database/ │ ├── FirebaseClient.cs │ ├── FirebaseKeyGenerator.cs │ ├── FirebaseObject.cs │ ├── GlobalSuppressions.cs │ ├── Http/ │ │ ├── HttpClientExtensions.cs │ │ └── PostResult.cs │ ├── ObservableExtensions.cs │ ├── Offline/ │ │ ├── ExceptionEventArgs.cs │ │ ├── OfflineCacheAdapter.cs │ │ ├── OfflineEntry.cs │ │ ├── RealtimeDatabase.cs │ │ └── SyncOptions.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── Query/ │ │ ├── AuthQuery.cs │ │ ├── ChildQuery.cs │ │ ├── FilterQuery.cs │ │ ├── FirebaseQuery.cs │ │ ├── IFirebaseQuery.cs │ │ ├── OrderQuery.cs │ │ ├── ParameterQuery.cs │ │ ├── QueryExtensions.cs │ │ └── QueryFactoryExtensions.cs │ └── Streaming/ │ ├── FirebaseCache.cs │ ├── FirebaseEvent.cs │ ├── FirebaseEventType.cs │ ├── FirebaseServerEventType.cs │ └── FirebaseSubscription.cs ├── Firebase.Xamarin.csproj ├── Firebase.Xamarin.sln ├── LICENSE ├── Notification/ │ └── PushNotification.cs ├── Properties/ │ └── AssemblyInfo.cs ├── README.md ├── Token/ │ ├── StreamToken.cs │ ├── TokenGenerator.cs │ └── TokenOptions.cs ├── TokenGenerator.config └── packages.config ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ #Autosave files *~ #build [Oo]bj/ [Bb]in/ packages/ TestResults/ # globs Makefile.in *.DS_Store *.sln.cache *.suo *.cache *.pidb *.userprefs *.usertasks config.log config.make config.status aclocal.m4 install-sh autom4te.cache/ *.user *.tar.gz tarballs/ test-results/ Thumbs.db #Mac bundle stuff *.dmg *.app #resharper *_Resharper.* *.Resharper #dotCover *.dotCover ================================================ FILE: Auth/FirebaseAuth.cs ================================================ namespace Firebase.Xamarin.Auth { using Newtonsoft.Json; /// /// The firebase auth. /// public class FirebaseAuth { /// /// Gets or sets the firebase token which can be used for authenticated queries. /// [JsonProperty("idToken")] public string FirebaseToken { get; set; } /// /// Gets or sets the refresh token of the underlying service which can be used to get a new access token. /// [JsonProperty("refreshToken")] public string RefreshToken { get; set; } /// /// Gets or sets the numbers of seconds until the token expires. /// [JsonProperty("expiresIn")] public int ExpiresIn { get; set; } /// /// Gets or sets the user. /// public User User { get; set; } } } ================================================ FILE: Auth/FirebaseAuthException.cs ================================================ using System; using System.Runtime.Serialization; namespace Firebase.Xamarin.Auth { /* * Sign In Exceptions */ public class FirebaseIncorrectPasswordException : Exception { /// /// Default constructor /// public FirebaseIncorrectPasswordException() : base() { } /// /// Argument constructor /// /// This is the description of the exception public FirebaseIncorrectPasswordException(String message) : base(message) { } /// /// Argument constructor with inner exception /// /// This is the description of the exception /// Inner exception public FirebaseIncorrectPasswordException(String message, Exception innerException) : base(message, innerException) { } } public class FirebaseInvalidEmailException : Exception { /// /// Default constructor /// public FirebaseInvalidEmailException() : base() { } /// /// Argument constructor /// /// This is the description of the exception public FirebaseInvalidEmailException(String message) : base(message) { } /// /// Argument constructor with inner exception /// /// This is the description of the exception /// Inner exception public FirebaseInvalidEmailException(String message, Exception innerException) : base(message, innerException) { } } /* * Create User Exceptions */ public class FirebaseUsedEmailException : Exception { /// /// Default constructor /// public FirebaseUsedEmailException() : base() { } /// /// Argument constructor /// /// This is the description of the exception public FirebaseUsedEmailException(String message) : base(message) { } /// /// Argument constructor with inner exception /// /// This is the description of the exception /// Inner exception public FirebaseUsedEmailException(String message, Exception innerException) : base(message, innerException) { } } public class FirebaseWeakPasswordException : Exception { /// /// Default constructor /// public FirebaseWeakPasswordException() : base() { } /// /// Argument constructor /// /// This is the description of the exception public FirebaseWeakPasswordException(String message) : base(message) { } /// /// Argument constructor with inner exception /// /// This is the description of the exception /// Inner exception public FirebaseWeakPasswordException(String message, Exception innerException) : base(message, innerException) { } } } ================================================ FILE: Auth/FirebaseAuthLink.cs ================================================ namespace Firebase.Xamarin.Auth { using System.Threading.Tasks; /// /// The firebase auth which can be linked to another credentials. /// public class FirebaseAuthLink : FirebaseAuth { internal FirebaseAuthLink() { } internal FirebaseAuthProvider AuthProvider { get; set; } /// /// Links the user with an email and password. /// /// The email. /// The password. /// The . public Task LinkToAsync(string email, string password) { return this.AuthProvider.LinkAccountsAsync(this, email, password); } /// /// Links the this user with and account from a third party provider. /// /// The auth type. /// The access token retrieved from login provider of your choice. /// The . public Task LinkToAsync(FirebaseAuthType authType, string oauthAccessToken) { return this.AuthProvider.LinkAccountsAsync(this, authType, oauthAccessToken); } } } ================================================ FILE: Auth/FirebaseAuthProvider.cs ================================================ namespace Firebase.Xamarin.Auth { using System; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; /// /// The auth token provider. /// public class FirebaseAuthProvider : IDisposable, IFirebaseAuthProvider { private const string GoogleIdentityUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion?key={0}"; private const string GoogleSignUpUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key={0}"; private const string GooglePasswordUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={0}"; private const string GooglePasswordResetUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key={0}"; private const string GoogleSetAccountUrl = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key={0}"; private readonly FirebaseConfig authConfig; private readonly HttpClient client; /// /// Initializes a new instance of the class. /// /// The auth config. public FirebaseAuthProvider(FirebaseConfig authConfig) { this.authConfig = authConfig; this.client = new HttpClient(); } /// /// Using the provided access token from third party auth provider (google, facebook...), get the firebase auth with token and basic user credentials. /// /// The auth type. /// The access token retrieved from login provider of your choice. /// The . public async Task SignInWithOAuthAsync(FirebaseAuthType authType, string oauthAccessToken) { var providerId = this.GetProviderId(authType); var content = $"{{\"postBody\":\"access_token={oauthAccessToken}&providerId={providerId}\",\"requestUri\":\"http://localhost\",\"returnSecureToken\":true}}"; return await this.SignInWithPostContentAsync(GoogleIdentityUrl, content).ConfigureAwait(false); } /// /// Sign in user anonymously. He would still have a user id and access token generated, but name and other personal user properties will be null. /// /// The . public async Task SignInAnonymouslyAsync() { var content = $"{{\"returnSecureToken\":true}}"; return await this.SignInWithPostContentAsync(GoogleSignUpUrl, content).ConfigureAwait(false); } /// /// Using the provided email and password, get the firebase auth with token and basic user credentials. /// /// The email. /// The password. /// The . public async Task SignInWithEmailAndPasswordAsync(string email, string password) { var content = $"{{\"email\":\"{email}\",\"password\":\"{password}\",\"returnSecureToken\":true}}"; return await this.SignInWithPostContentAsync(GooglePasswordUrl, content).ConfigureAwait(false); } /// /// Creates new user with given credentials. /// /// The email. /// The password. /// The . public async Task CreateUserWithEmailAndPasswordAsync(string email, string password) { var content = $"{{\"email\":\"{email}\",\"password\":\"{password}\",\"returnSecureToken\":true}}"; return await this.SignInWithPostContentAsync(GoogleSignUpUrl, content).ConfigureAwait(false); } /// /// Sends user an email with a link to reset his password. /// /// The email. public async Task SendPasswordResetEmailAsync(string email) { var content = $"{{\"requestType\":\"PASSWORD_RESET\",\"email\":\"{email}\"}}"; var response = await this.client.PostAsync(new Uri(string.Format(GooglePasswordResetUrl, this.authConfig.ApiKey)), new StringContent(content, Encoding.UTF8, "application/json")).ConfigureAwait(false); response.EnsureSuccessStatusCode(); } /// /// Links the authenticated user represented by with an email and password. /// /// The authenticated user to link with specified email and password. /// The email. /// The password. /// The . public async Task LinkAccountsAsync(FirebaseAuth auth, string email, string password) { var content = $"{{\"idToken\":\"{auth.FirebaseToken}\",\"email\":\"{email}\",\"password\":\"{password}\",\"returnSecureToken\":true}}"; return await this.SignInWithPostContentAsync(GoogleSetAccountUrl, content).ConfigureAwait(false); } /// /// Links the authenticated user represented by with and account from a third party provider. /// /// The auth. /// The auth type. /// The access token retrieved from login provider of your choice. /// The . public async Task LinkAccountsAsync(FirebaseAuth auth, FirebaseAuthType authType, string oauthAccessToken) { var providerId = this.GetProviderId(authType); var content = $"{{\"idToken\":\"{auth.FirebaseToken}\",\"postBody\":\"access_token={oauthAccessToken}&providerId={providerId}\",\"requestUri\":\"http://localhost\",\"returnSecureToken\":true}}"; return await this.SignInWithPostContentAsync(GoogleIdentityUrl, content).ConfigureAwait(false); } /// /// Disposes all allocated resources. /// public void Dispose() { this.client.Dispose(); } private async Task SignInWithPostContentAsync(string googleUrl, string postContent) { var response = await this.client.PostAsync(new Uri(string.Format(googleUrl, this.authConfig.ApiKey)), new StringContent(postContent, Encoding.UTF8, "application/json")).ConfigureAwait(false); var responseData = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) { var jsonReturn = JObject.Parse(responseData); var message = (string)jsonReturn["error"]["message"]; // Login // Email address not found in database if (message.Equals("EMAIL_NOT_FOUND")) { throw new FirebaseInvalidEmailException("Email address not found"); } // Login // Invalid password supplied else if (message.Equals("INVALID_PASSWORD")) { throw new FirebaseIncorrectPasswordException("Incorrect passord"); } // New User // Email address already exists else if (message.Equals("EMAIL_EXISTS")) { throw new FirebaseUsedEmailException("Email address already exists"); } // New User // Week Password else if (message.Contains("WEAK_PASSWORD")) { throw new FirebaseWeakPasswordException("Weak password, must be at least 6 characters"); } // Just end on default status check else { response.EnsureSuccessStatusCode(); } } var user = JsonConvert.DeserializeObject(responseData); var auth = JsonConvert.DeserializeObject(responseData); auth.User = user; auth.AuthProvider = this; return auth; } private string GetProviderId(FirebaseAuthType authType) { switch (authType) { case FirebaseAuthType.Facebook: return "facebook.com"; case FirebaseAuthType.Google: return "google.com"; case FirebaseAuthType.Github: return "github.com"; case FirebaseAuthType.Twitter: return "twitter.com"; default: throw new NotImplementedException(""); } } } } ================================================ FILE: Auth/FirebaseAuthType.cs ================================================ namespace Firebase.Xamarin.Auth { /// /// The type of authentication. /// public enum FirebaseAuthType { /// /// The facebook auth. /// Facebook, /// /// The google auth. /// Google, /// /// The github auth. /// Github, /// /// The twitter auth. /// Twitter } } ================================================ FILE: Auth/FirebaseConfig.cs ================================================ namespace Firebase.Xamarin.Auth { /// /// The auth config. /// public class FirebaseConfig { /// /// Initializes a new instance of the class. /// /// The api key of your Firebase app. public FirebaseConfig(string apiKey) { this.ApiKey = apiKey; } /// /// Initializes a new instance of the class. /// /// API key. /// API key for push notification. public FirebaseConfig(string apiKey, string apiKeyForPushNotification) { this.ApiKey = apiKey; this.ApiKeyForPushNotification = apiKeyForPushNotification; } /// /// Gets or sets the api key of your Firebase app. /// public string ApiKey { get; set; } /// /// Gets or sets the API key for push notification. /// /// The API key for push notification. public string ApiKeyForPushNotification { get; set; } } } ================================================ FILE: Auth/IFirebaseAuthProvider.cs ================================================ namespace Firebase.Xamarin.Auth { using System.Threading.Tasks; /// /// The auth token provider. /// public interface IFirebaseAuthProvider { /// /// Creates new user with given credentials. /// /// The email. /// The password. /// The . Task CreateUserWithEmailAndPasswordAsync(string email, string password); /// /// Sends user an email with a link to reset his password. /// /// The email. /// The . Task SendPasswordResetEmailAsync(string email); /// /// Sign in user anonymously. He would still have a user id and access token generated, but name and other personal user properties will be null. /// /// The . Task SignInAnonymouslyAsync(); /// /// Using the provided email and password, get the firebase auth with token and basic user credentials. /// /// The email. /// The password. /// The . Task SignInWithEmailAndPasswordAsync(string email, string password); /// /// Using the provided access token from third party auth provider (google, facebook...), get the firebase auth with token and basic user credentials. /// /// The auth type. /// The access token retrieved from login provider of your choice. /// The . Task SignInWithOAuthAsync(FirebaseAuthType authType, string oauthAccessToken); /// /// Links the authenticated user represented by with an email and password. /// /// The authenticated user to link with specified email and password. /// The email. /// The password. /// The . Task LinkAccountsAsync(FirebaseAuth auth, string email, string password); /// /// Links the authenticated user represented by with and account from a third party provider. /// /// The auth. /// The auth type. /// The access token retrieved from login provider of your choice. /// The . Task LinkAccountsAsync(FirebaseAuth auth, FirebaseAuthType authType, string oauthAccessToken); } } ================================================ FILE: Auth/User.cs ================================================ namespace Firebase.Xamarin.Auth { using System.ComponentModel; using Newtonsoft.Json; /// /// Basic information about the logged in user. /// public class User { /// /// Gets or sets the local id. /// [JsonProperty("localId", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string LocalId { get; set; } /// /// Gets or sets the federated id. /// [JsonProperty("federatedId", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string FederatedId { get; set; } /// /// Gets or sets the first name. /// [JsonProperty("firstName", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string FirstName { get; set; } /// /// Gets or sets the last name. /// [JsonProperty("lastName", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string LastName { get; set; } /// /// Gets or sets the display name. /// [JsonProperty("displayName", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string DisplayName { get; set; } /// /// Gets or sets the email. /// [JsonProperty("email", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string Email { get; set; } /// /// Gets or sets the photo url. /// [JsonProperty("photoUrl", DefaultValueHandling = DefaultValueHandling.Populate)] [DefaultValue("")] public string PhotoUrl { get; set; } } } ================================================ FILE: Database/FirebaseClient.cs ================================================ namespace Firebase.Xamarin.Database { using System; using System.Collections.Generic; using System.Threading.Tasks; using Firebase.Xamarin.Database.Offline; using Firebase.Xamarin.Database.Query; /// /// Firebase client which acts as an entry point to the online database. /// public class FirebaseClient { internal readonly Func> OfflineDatabaseFactory; internal readonly Func> AuthTokenAsyncFactory; private readonly string baseUrl; /// /// Initializes a new instance of the class. /// /// The base url. public FirebaseClient(string baseUrl) : this(baseUrl, (t, s) => new Dictionary()) { } /// /// Initializes a new instance of the class. /// /// The base url. /// Offline database. public FirebaseClient(string baseUrl, Func> offlineDatabaseFactory) { this.OfflineDatabaseFactory = offlineDatabaseFactory; this.baseUrl = baseUrl; if (!this.baseUrl.EndsWith("/")) { this.baseUrl += "/"; } } /// /// Initializes a new instance of the class. /// /// The base url. /// Factory which returns valid firebase auth token. public FirebaseClient(string baseUrl, Func> authTokenAsyncFactory) : this(baseUrl, authTokenAsyncFactory, (t, s) => new Dictionary()) { } /// /// Initializes a new instance of the class. /// /// The base url. /// Factory which returns valid firebase auth token. /// Offline database. public FirebaseClient(string baseUrl, Func> authTokenAsyncFactory, Func> offlineDatabaseFactory) : this(baseUrl, offlineDatabaseFactory) { this.AuthTokenAsyncFactory = authTokenAsyncFactory; } /// /// Queries for a child of the data root. /// /// Name of the child. /// . public ChildQuery Child(string resourceName) { return new ChildQuery(this, () => this.baseUrl + resourceName); } } } ================================================ FILE: Database/FirebaseKeyGenerator.cs ================================================ namespace Firebase.Xamarin.Database { using System; using System.Text; /// /// Offline key generator which mimics the official Firebase generators. /// Credit: https://github.com/bubbafat/FirebaseSharp/blob/master/src/FirebaseSharp.Portable/FireBasePushIdGenerator.cs /// public class FirebaseKeyGenerator { // Modeled after base64 web-safe chars, but ordered by ASCII. private const string PushCharsString = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; private static readonly char[] PushChars; private static readonly DateTimeOffset Epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero); private static readonly Random random = new Random(); private static readonly byte[] lastRandChars = new byte[12]; // Timestamp of last push, used to prevent local collisions if you push twice in one ms. private static long lastPushTime; static FirebaseKeyGenerator() { PushChars = Encoding.UTF8.GetChars(Encoding.UTF8.GetBytes(PushCharsString)); } /// /// Returns next firebase key based on current time. /// /// /// The . public static string Next() { // We generate 72-bits of randomness which get turned into 12 characters and // appended to the timestamp to prevent collisions with other clients. We store the last // characters we generated because in the event of a collision, we'll use those same // characters except "incremented" by one. var id = new StringBuilder(20); var now = (long)(DateTimeOffset.Now - Epoch).TotalMilliseconds; var duplicateTime = now == lastPushTime; lastPushTime = now; var timeStampChars = new char[8]; for (int i = 7; i >= 0; i--) { var index = (int)(now % PushChars.Length); timeStampChars[i] = PushChars[index]; now = (long)Math.Floor((double)now / PushChars.Length); } if (now != 0) { throw new Exception("We should have converted the entire timestamp."); } id.Append(timeStampChars); if (!duplicateTime) { for (int i = 0; i < 12; i++) { lastRandChars[i] = (byte)random.Next(0, PushChars.Length); } } else { // If the timestamp hasn't changed since last push, use the same random number, // except incremented by 1. var lastIndex = 11; for (; lastIndex >= 0 && lastRandChars[lastIndex] == PushChars.Length - 1; lastIndex--) { lastRandChars[lastIndex] = 0; } lastRandChars[lastIndex]++; } for (int i = 0; i < 12; i++) { id.Append(PushChars[lastRandChars[i]]); } if (id.Length != 20) { throw new Exception("Length should be 20."); } return id.ToString(); } } } ================================================ FILE: Database/FirebaseObject.cs ================================================ namespace Firebase.Xamarin.Database { /// /// Holds the object of type along with its key. /// /// Type of the underlying object. public class FirebaseObject { internal FirebaseObject(string key, T obj) { this.Key = key; this.Object = obj; } /// /// Gets the key of . /// public string Key { get; } /// /// Gets the underlying object. /// public T Object { get; } } } ================================================ FILE: Database/GlobalSuppressions.cs ================================================  // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. ================================================ FILE: Database/Http/HttpClientExtensions.cs ================================================ namespace Firebase.Xamarin.Database.Http { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; /// /// The http client extensions for object deserializations. /// internal static class HttpClientExtensions { /// /// The get object collection async. /// /// The client. /// The request uri. /// The type of entities the collection should contain. /// The . public static async Task>> GetObjectCollectionAsync(this HttpClient client, string requestUri) { var data = await client.GetStringAsync(requestUri).ConfigureAwait(false); var dictionary = JsonConvert.DeserializeObject>(data); if (dictionary == null) { return new FirebaseObject[0]; } return dictionary.Select(item => new FirebaseObject(item.Key, item.Value)).ToList(); } /// /// The get object collection async. /// /// The json data. /// The type of entities the collection should contain. /// The . public static IEnumerable> GetObjectCollection(this string data, Type elementType) { var dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), elementType); var dictionary = JsonConvert.DeserializeObject(data, dictionaryType) as IDictionary; if (dictionary == null) { yield break; } foreach (DictionaryEntry item in dictionary) { yield return new FirebaseObject((string)item.Key, item.Value); } } } } ================================================ FILE: Database/Http/PostResult.cs ================================================ namespace Firebase.Xamarin.Database.Http { /// /// Represents data returned after a successful POST to firebase server. /// public class PostResult { /// /// Gets or sets the generated key after a successful post. /// public string Name { get; set; } } } ================================================ FILE: Database/ObservableExtensions.cs ================================================ namespace Firebase.Xamarin.Database { using System; using System.Collections.ObjectModel; using Firebase.Xamarin.Database.Streaming; /// /// Extensions for . /// public static class ObservableExtensions { /// /// Starts observing on given firebase observable and propagates event into an . /// /// The observable. /// Type of entity. /// The . public static ObservableCollection AsObservableCollection(this IObservable> observable) { var collection = new ObservableCollection(); observable.Subscribe(f => { if (f.EventType == FirebaseEventType.InsertOrUpdate) { var i = collection.IndexOf(f.Object); if (i >= 0) { collection.RemoveAt(i); } collection.Add(f.Object); } else { collection.Remove(f.Object); } }); return collection; } } } ================================================ FILE: Database/Offline/ExceptionEventArgs.cs ================================================ namespace Firebase.Xamarin.Database.Offline { using System; /// /// Event args holding the object. /// public class ExceptionEventArgs : EventArgs { public readonly Exception Exception; /// /// Initializes a new instance of the class. /// /// The exception. public ExceptionEventArgs(Exception exception) { this.Exception = exception; } } } ================================================ FILE: Database/Offline/OfflineCacheAdapter.cs ================================================ namespace Firebase.Xamarin.Database.Offline { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal class OfflineCacheAdapter : IDictionary, IDictionary { private readonly IDictionary database; public OfflineCacheAdapter(IDictionary database) { this.database = database; } public void CopyTo(Array array, int index) { throw new NotImplementedException(); } public int Count => this.database.Count; public bool IsSynchronized { get; } public object SyncRoot { get; } public bool IsReadOnly => this.database.IsReadOnly; object IDictionary.this[object key] { get { return this.database[key.ToString()].Deserialize(); } set { this.database[key.ToString()] = new OfflineEntry(key.ToString(), value, 1, SyncOptions.None); } } public ICollection Keys => this.database.Keys; ICollection IDictionary.Values { get; } ICollection IDictionary.Keys { get; } public ICollection Values => this.database.Values.Select(o => o.Deserialize()).ToList(); public T this[string key] { get { return this.database[key].Deserialize(); } set { this.database[key] = new OfflineEntry(key, value, 1, SyncOptions.None); } } public bool Contains(object key) { return this.ContainsKey(key.ToString()); } IDictionaryEnumerator IDictionary.GetEnumerator() { throw new NotImplementedException(); } public void Remove(object key) { this.Remove(key.ToString()); } public bool IsFixedSize => false; public IEnumerator> GetEnumerator() { return this.database.Select(d => new KeyValuePair(d.Key, d.Value.Deserialize())).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public void Add(KeyValuePair item) { this.Add(item.Key, item.Value); } public void Add(object key, object value) { this.Add(key.ToString(), (T)value); } public void Clear() { this.database.Clear(); } public bool Contains(KeyValuePair item) { return this.ContainsKey(item.Key); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { throw new NotImplementedException(); } public bool Remove(KeyValuePair item) { return this.database.Remove(item.Key); } public void Add(string key, T value) { this.database.Add(key, new OfflineEntry(key, value, 1, SyncOptions.None)); } public bool ContainsKey(string key) { return this.database.ContainsKey(key); } public bool Remove(string key) { return this.database.Remove(key); } public bool TryGetValue(string key, out T value) { OfflineEntry val; if (this.database.TryGetValue(key, out val)) { value = val.Deserialize(); return true; } value = default(T); return false; } } } ================================================ FILE: Database/Offline/OfflineEntry.cs ================================================ namespace Firebase.Xamarin.Database.Offline { using System; using Newtonsoft.Json; /// /// Represents an object stored in offline storage. /// public class OfflineEntry { private object dataInstance; /// /// Initializes a new instance of the class. /// /// The key. /// The object. /// The priority. Objects with higher priority will be synced first. Higher number indicates higher priority. /// The sync options. public OfflineEntry(string key, object obj, int priority, SyncOptions syncOptions = SyncOptions.Push) { this.Key = key; this.Priority = priority; this.Data = JsonConvert.SerializeObject(obj); this.Timestamp = DateTime.UtcNow; this.SyncOptions = syncOptions; this.dataInstance = obj; } /// /// Initializes a new instance of the class. /// public OfflineEntry() { } /// /// Gets or sets the key of this entry. /// public string Key { get; set; } /// /// Gets or sets the priority. Objects with higher priority will be synced first. Higher number indicates higher priority. /// public int Priority { get; set; } /// /// Gets or sets the timestamp when this entry was last touched. /// public DateTime Timestamp { get; set; } /// /// Gets or sets the which define what sync state this entry is in. /// public SyncOptions SyncOptions { get; set; } /// /// Gets or sets serialized JSON data. /// public string Data { get; set; } /// /// Deserializes into . The result is cached. /// /// Type of object to deserialize into. /// Instance of . public T Deserialize() { return (T)(this.dataInstance ?? (this.dataInstance = JsonConvert.DeserializeObject(this.Data))); } } } ================================================ FILE: Database/Offline/RealtimeDatabase.cs ================================================ namespace Firebase.Xamarin.Database.Offline { using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; using Firebase.Xamarin.Database.Query; using Firebase.Xamarin.Database.Streaming; /// /// The real-time database which synchronizes online and offline data. /// /// Type of entities. public partial class RealtimeDatabase where T : class { private readonly ChildQuery childQuery; private readonly bool streamChanges; private readonly IDictionary database; private readonly string elementRoot; private readonly Subject> subject; private IObservable> observable; /// /// Initializes a new instance of the class. /// /// The child query. /// The element Root. /// The offline database factory. /// Custom string which will get appended to the file name. /// Specifies whether changes should be streamed from the server. public RealtimeDatabase(ChildQuery childQuery, string elementRoot, Func> offlineDatabaseFactory, string filenameModifier, bool streamChanges) { this.childQuery = childQuery; this.elementRoot = elementRoot; this.streamChanges = streamChanges; this.database = offlineDatabaseFactory(typeof(T), filenameModifier); this.subject = new Subject>(); Task.Factory.StartNew(this.SynchronizeThread, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); } /// /// Event raised whenever an exception is thrown in the synchronization thread. Exception thrown in there are swallowed, so this event is the only way to get to them. /// public event EventHandler SyncExceptionThrown; /// /// Overwrites existing object with given key. /// /// The key. /// The object to set. /// The priority. Objects with higher priority will be synced first. Higher number indicates higher priority. public void Put(string key, T obj, int priority = 1) { this.SetAndRaise(key, new OfflineEntry(key, obj, priority)); } /// /// Adds a new entity to the database. /// /// The object to add. /// The priority. Objects with higher priority will be synced first. Higher number indicates higher priority. /// The generated key for this object. public string Post(T obj, int priority = 1) { var key = FirebaseKeyGenerator.Next(); this.SetAndRaise(key, new OfflineEntry(key, obj, priority)); return key; } /// /// Deletes the entity with the given key. /// /// The key. /// The priority. Objects with higher priority will be synced first. Higher number indicates higher priority. public void Delete(string key, int priority = 1) { this.SetAndRaise(key, new OfflineEntry(key, null, priority)); } /// /// Fetches an object with the given key and adds it to the database. /// /// The key. /// The priority. Objects with higher priority will be synced first. Higher number indicates higher priority. public void Pull(string key, int priority = 1) { if (!this.database.ContainsKey(key)) { this.database[key] = new OfflineEntry(key, null, priority, SyncOptions.Pull); } else { this.database[key].SyncOptions = SyncOptions.Pull; } } /// /// Starts observing the real-time database. Events will be fired both when change is done locally and remotely. /// /// Stream of . public IObservable> AsObservable() { if (this.observable == null) { var initialData = this.database .Where(kvp => !string.IsNullOrEmpty(kvp.Value.Data) && kvp.Value.Data != "null") .Select(kvp => new FirebaseEvent(kvp.Key, kvp.Value.Deserialize(), FirebaseEventType.InsertOrUpdate)) .ToList().ToObservable(); this.observable = Observable .Create>(observer => this.InitializeStreamingSubscription(observer)) .Merge(initialData) .Merge(this.subject) .Replay() .RefCount(); } return this.observable; } private IDisposable InitializeStreamingSubscription(IObserver> observer) { return this.streamChanges ? new FirebaseSubscription(observer, this.childQuery.OrderByKey().StartAt(() => this.GetLatestKey()), this.elementRoot, new FirebaseCache(new OfflineCacheAdapter(this.database))).Run() : Observable.Never().Subscribe(); } private void SetAndRaise(string key, OfflineEntry obj) { this.database[key] = obj; this.subject.OnNext(new FirebaseEvent(key, obj?.Deserialize(), string.IsNullOrEmpty(obj?.Data) ? FirebaseEventType.Delete : FirebaseEventType.InsertOrUpdate)); } private async void SynchronizeThread() { while (true) { try { var validEntries = this.database.Where(e => e.Value != null); await this.PullEntriesAsync(validEntries.Where(kvp => kvp.Value.SyncOptions == SyncOptions.Pull)); await this.PushEntriesAsync(validEntries.Where(kvp => kvp.Value.SyncOptions == SyncOptions.Push)); } catch (Exception ex) { this.SyncExceptionThrown?.Invoke(this, new ExceptionEventArgs(ex)); } await Task.Delay(1000); } } private string GetLatestKey() { return this.database.OrderBy(o => o.Key, StringComparer.Ordinal).LastOrDefault().Key; } private async Task PushEntriesAsync(IEnumerable> pushEntries) { var groups = pushEntries.GroupBy(pair => pair.Value.Priority).OrderByDescending(kvp => kvp.Key); foreach (var group in groups) { var tasks = group.Select(kvp => this.childQuery.Child(kvp.Key).PutAsync(kvp.Value.Deserialize())).ToList(); await Task.WhenAll(tasks); this.ResetSyncOptions(group.Select(s => s.Key)); } } private async Task PullEntriesAsync(IEnumerable> pullEntries) { var taskGroups = pullEntries.GroupBy(pair => pair.Value.Priority).OrderByDescending(kvp => kvp.Key); foreach (var group in taskGroups) { var tasks = group.Select(pair => new { Key = pair.Key, Task = this.childQuery.Child(pair.Key).OnceSingleAsync(), Priority = pair.Value.Priority }).ToList(); await Task.WhenAll(tasks.Select(t => t.Task)); foreach (var task in tasks) { this.SetAndRaise(task.Key, new OfflineEntry(task.Key, task.Task.Result, task.Priority, SyncOptions.None)); } } } private void ResetSyncOptions(IEnumerable entries) { foreach (var key in entries) { var item = this.database[key]; item.SyncOptions = SyncOptions.None; this.database[key] = item; } } } } ================================================ FILE: Database/Offline/SyncOptions.cs ================================================ namespace Firebase.Xamarin.Database.Offline { /// /// Specifies type of sync requested for given data. /// public enum SyncOptions { /// /// No sync needed for given data. /// None, /// /// Data should be pulled from firebase. /// Pull, /// /// Data should be pushed to firebase. /// Push } } ================================================ FILE: Database/Properties/AssemblyInfo.cs ================================================ using System.Resources; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Firebase.Database")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Step Up Labs, Inc.")] [assembly: AssemblyProduct("Firebase.Database")] [assembly: AssemblyCopyright("Copyright © Step Up Labs, Inc. 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: InternalsVisibleTo("Firebase.Database.Tests")] ================================================ FILE: Database/Query/AuthQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; /// /// Represents an auth parameter in firebase query, e.g. "?auth=xyz". /// public class AuthQuery : ParameterQuery { private readonly Func tokenFactory; /// /// Initializes a new instance of the class. /// /// The parent. /// The authentication token factory. /// The owner. public AuthQuery(FirebaseQuery parent, Func tokenFactory, FirebaseClient client) : base(parent, () => "auth", client) { this.tokenFactory = tokenFactory; } /// /// Build the url parameter value of this child. /// /// The child of this child. /// The . protected override string BuildUrlParameter(FirebaseQuery child) { return this.tokenFactory(); } } } ================================================ FILE: Database/Query/ChildQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; using Firebase.Xamarin.Database; using Firebase.Xamarin.Database.Offline; /// /// Firebase query which references the child of current node. /// public class ChildQuery : FirebaseQuery { private readonly Func pathFactory; /// /// Initializes a new instance of the class. /// /// The parent. /// The path to the child node. /// The owner. public ChildQuery(FirebaseQuery parent, Func pathFactory, FirebaseClient client) : base(parent, client) { this.pathFactory = pathFactory; } /// /// Initializes a new instance of the class. /// /// The client. /// The path to the child node. public ChildQuery(FirebaseClient client, Func pathFactory) : this(null, pathFactory, client) { } /// /// The as offline database. /// /// Type of elements. /// Custom string which will get appended to the file name. /// Optional custom root element of received json items. /// Specifies whether changes should be streamed from the server. /// The . public RealtimeDatabase AsRealtimeDatabase(string filenameModifier, string elementRoot = "", bool streamChanges = true) where T : class { return new RealtimeDatabase(this, elementRoot, this.Client.OfflineDatabaseFactory, filenameModifier, streamChanges); } /// /// Build the url segment of this child. /// /// The child of this child. /// The . protected override string BuildUrlSegment(FirebaseQuery child) { var s = this.pathFactory(); if (s != string.Empty && !s.EndsWith("/")) { s += '/'; } if (!(child is ChildQuery)) { return s + ".json"; } return s; } } } ================================================ FILE: Database/Query/FilterQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; using System.Globalization; /// /// Represents a firebase filtering query, e.g. "?LimitToLast=10". /// public class FilterQuery : ParameterQuery { private readonly Func valueFactory; private readonly Func doubleValueFactory; /// /// Initializes a new instance of the class. /// /// The parent. /// The filter. /// The value for filter. /// The owning client. public FilterQuery(FirebaseQuery parent, Func filterFactory, Func valueFactory, FirebaseClient client) : base(parent, filterFactory, client) { this.valueFactory = valueFactory; } /// /// Initializes a new instance of the class. /// /// The parent. /// The filter. /// The value for filter. /// The owning client. public FilterQuery(FirebaseQuery parent, Func filterFactory, Func valueFactory, FirebaseClient client) : base(parent, filterFactory, client) { this.doubleValueFactory = valueFactory; } /// /// The build url parameter. /// /// The child. /// Url parameter part of the resulting path. protected override string BuildUrlParameter(FirebaseQuery child) { if (this.valueFactory != null) { return $"\"{this.valueFactory()}\""; } else if (this.doubleValueFactory != null) { return this.doubleValueFactory().ToString(CultureInfo.InvariantCulture); } return string.Empty; } } } ================================================ FILE: Database/Query/FirebaseQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; using System.Collections.Generic; using System.Net.Http; using System.Reactive.Linq; using System.Threading.Tasks; using Firebase.Xamarin.Database.Http; using Firebase.Xamarin.Database.Offline; using Firebase.Xamarin.Database.Streaming; using Newtonsoft.Json; /// /// Represents a firebase query. /// public abstract class FirebaseQuery : IFirebaseQuery, IDisposable { protected readonly FirebaseQuery Parent; private HttpClient client; /// /// Initializes a new instance of the class. /// /// The parent of this query. /// The owning client. protected FirebaseQuery(FirebaseQuery parent, FirebaseClient client) { this.Client = client; this.Parent = parent; } /// /// Gets the client. /// public FirebaseClient Client { get; } /// /// Queries the firebase server once returning collection of items. /// /// Type of elements. /// Collection of holding the entities returned by server. public async Task>> OnceAsync() { var path = await this.BuildUrlAsync().ConfigureAwait(false); using (var client = new HttpClient()) { return await client.GetObjectCollectionAsync(path).ConfigureAwait(false); } } /// /// Assumes given query is pointing to a single object of type and retrieves it. /// /// Type of elements. /// Single object of type . public async Task OnceSingleAsync() { var path = await this.BuildUrlAsync().ConfigureAwait(false); using (var client = new HttpClient()) { var data = await client.GetStringAsync(path).ConfigureAwait(false); return JsonConvert.DeserializeObject(data); } } /// /// Starts observing this query watching for changes real time sent by the server. /// /// Type of elements. /// Optional custom root element of received json items. /// Observable stream of . public IObservable> AsObservable(string elementRoot = "") { return Observable.Create>(observer => new FirebaseSubscription(observer, this, elementRoot, new FirebaseCache()).Run()); } /// /// Builds the actual URL of this query. /// /// The . public async Task BuildUrlAsync() { // if token factory is present on the parent then use it to generate auth token if (this.Client.AuthTokenAsyncFactory != null) { var token = await this.Client.AuthTokenAsyncFactory().ConfigureAwait(false); return this.WithAuth(token).BuildUrl(null); } return this.BuildUrl(null); } /// /// Posts given object to repository. /// /// The object. /// Specifies whether the key should be generated offline instead of online. /// Type of /// Resulting firebase object with populated key. public async Task> PostAsync(T obj, bool generateKeyOffline = true) { // post generates a new key server-side, while put can be used with an already generated local key if (generateKeyOffline) { var key = FirebaseKeyGenerator.Next(); await new ChildQuery(this, () => key, this.Client).PutAsync(obj).ConfigureAwait(false); return new FirebaseObject(key, obj); } else { var c = this.GetClient(); var data = await this.SendAsync(c, obj, HttpMethod.Post).ConfigureAwait(false); var result = JsonConvert.DeserializeObject(data); return new FirebaseObject(result.Name, obj); } } /// /// Patches data at given location instead of overwriting them. /// /// The object. /// Type of /// The . public async Task PatchAsync(T obj) { var c = this.GetClient(); await this.SendAsync(c, obj, new HttpMethod("PATCH")).ConfigureAwait(false); } /// /// Sets or overwrites data at given location. /// /// The object. /// Type of /// The . public async Task PutAsync(T obj) { var c = this.GetClient(); await this.SendAsync(c, obj, HttpMethod.Put).ConfigureAwait(false); } /// /// Deletes data from given location. /// /// The . public async Task DeleteAsync() { var c = this.GetClient(); var url = await this.BuildUrlAsync().ConfigureAwait(false); var result = await c.DeleteAsync(url).ConfigureAwait(false); result.EnsureSuccessStatusCode(); } /// /// Disposes this instance. /// public void Dispose() { this.client?.Dispose(); } /// /// Build the url segment of this child. /// /// The child of this query. /// The . protected abstract string BuildUrlSegment(FirebaseQuery child); private string BuildUrl(FirebaseQuery child) { var url = this.BuildUrlSegment(child); if (this.Parent != null) { url = this.Parent.BuildUrl(this) + url; } return url; } private HttpClient GetClient() { if (this.client == null) { this.client = new HttpClient(); } return this.client; } private async Task SendAsync(HttpClient client, T obj, HttpMethod method) { var url = await this.BuildUrlAsync().ConfigureAwait(false); var message = new HttpRequestMessage(method, url) { Content = new StringContent(JsonConvert.SerializeObject(obj)) }; var result = await client.SendAsync(message).ConfigureAwait(false); result.EnsureSuccessStatusCode(); return await result.Content.ReadAsStringAsync().ConfigureAwait(false); } } } ================================================ FILE: Database/Query/IFirebaseQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; using System.Collections.Generic; using System.Threading.Tasks; using Firebase.Xamarin.Database.Streaming; /// /// The FirebaseQuery interface. /// public interface IFirebaseQuery { /// /// Retrieves items which exist on the location specified by this query instance. /// /// Type of the items. /// Collection of . Task>> OnceAsync(); /// /// Returns current location as an observable which allows to real-time listening to events from the firebase server. /// /// Type of the items. /// Cold observable of . IObservable> AsObservable(string elementRoot = ""); /// /// Builds the actual url of this query. /// /// The . Task BuildUrlAsync(); } } ================================================ FILE: Database/Query/OrderQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; /// /// Represents a firebase ordering query, e.g. "?OrderBy=Foo". /// public class OrderQuery : ParameterQuery { private readonly Func propertyNameFactory; /// /// Initializes a new instance of the class. /// /// The query parent. /// The property name. /// The owning client. public OrderQuery(ChildQuery parent, Func propertyNameFactory, FirebaseClient client) : base(parent, () => "orderBy", client) { this.propertyNameFactory = propertyNameFactory; } /// /// The build url parameter. /// /// The child. /// The . protected override string BuildUrlParameter(FirebaseQuery child) { return $"\"{this.propertyNameFactory()}\""; } } } ================================================ FILE: Database/Query/ParameterQuery.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; /// /// Represents a parameter in firebase query, e.g. "?data=foo". /// public abstract class ParameterQuery : FirebaseQuery { private readonly Func parameterFactory; private readonly string separator; /// /// Initializes a new instance of the class. /// /// The parent of this query. /// The parameter. /// The owning client. protected ParameterQuery(FirebaseQuery parent, Func parameterFactory, FirebaseClient client) : base(parent, client) { this.parameterFactory = parameterFactory; this.separator = (this.Parent is ChildQuery) ? "?" : "&"; } /// /// Build the url segment represented by this query. /// /// The child. /// The . protected override string BuildUrlSegment(FirebaseQuery child) { return $"{this.separator}{this.parameterFactory()}={this.BuildUrlParameter(child)}"; } /// /// The build url parameter. /// /// The child. /// The . protected abstract string BuildUrlParameter(FirebaseQuery child); } } ================================================ FILE: Database/Query/QueryExtensions.cs ================================================ namespace Firebase.Xamarin.Database.Query { /// /// Query extensions providing linq like syntax for firebase server methods. /// public static class QueryExtensions { /// /// Adds an auth parameter to the query. /// /// The child. /// The auth token. /// The . public static AuthQuery WithAuth(this FirebaseQuery node, string token) { return node.WithAuth(() => token); } /// /// References a sub child of the existing node. /// /// The child. /// The path of sub child. /// The . public static ChildQuery Child(this ChildQuery node, string path) { return node.Child(() => path); } /// /// Order data by given . Note that this is used mainly for following filtering queries and due to firebase implementation /// the data may actually not be ordered. /// /// The child. /// The property name. /// The . public static OrderQuery OrderBy(this ChildQuery child, string propertyName) { return child.OrderBy(() => propertyName); } /// /// Instructs firebase to send data greater or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery StartAt(this ParameterQuery child, string value) { return child.StartAt(() => value); } /// /// Instructs firebase to send data lower or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EndAt(this ParameterQuery child, string value) { return child.EndAt(() => value); } /// /// Instructs firebase to send data equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EqualTo(this ParameterQuery child, string value) { return child.EqualTo(() => value); } /// /// Instructs firebase to send data greater or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery StartAt(this ParameterQuery child, double value) { return child.StartAt(() => value); } /// /// Instructs firebase to send data lower or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EndAt(this ParameterQuery child, double value) { return child.EndAt(() => value); } /// /// Instructs firebase to send data equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EqualTo(this ParameterQuery child, double value) { return child.EqualTo(() => value); } /// /// Limits the result to first items. /// /// Current node. /// Number of elements. /// The . public static FilterQuery LimitToFirst(this ParameterQuery child, int count) { return child.LimitToFirst(() => count); } /// /// Limits the result to last items. /// /// Current node. /// Number of elements. /// The . public static FilterQuery LimitToLast(this ParameterQuery child, int count) { return child.LimitToLast(() => count); } } } ================================================ FILE: Database/Query/QueryFactoryExtensions.cs ================================================ namespace Firebase.Xamarin.Database.Query { using System; /// /// Query extensions providing linq like syntax for firebase server methods. /// public static class QueryFactoryExtensions { /// /// Adds an auth parameter to the query. /// /// The child. /// The auth token. /// The . public static AuthQuery WithAuth(this FirebaseQuery node, Func tokenFactory) { return new AuthQuery(node, tokenFactory, node.Client); } /// /// References a sub child of the existing node. /// /// The child. /// The path of sub child. /// The . public static ChildQuery Child(this ChildQuery node, Func pathFactory) { return new ChildQuery(node, pathFactory, node.Client); } /// /// Order data by given . Note that this is used mainly for following filtering queries and due to firebase implementation /// the data may actually not be ordered. /// /// The child. /// The property name. /// The . public static OrderQuery OrderBy(this ChildQuery child, Func propertyNameFactory) { return new OrderQuery(child, propertyNameFactory, child.Client); } /// /// Order data by $key. Note that this is used mainly for following filtering queries and due to firebase implementation /// the data may actually not be ordered. /// /// The child. /// The . public static OrderQuery OrderByKey(this ChildQuery child) { return child.OrderBy("$key"); } /// /// Order data by $value. Note that this is used mainly for following filtering queries and due to firebase implementation /// the data may actually not be ordered. /// /// The child. /// The . public static OrderQuery OrderByValue(this ChildQuery child) { return child.OrderBy("$value"); } /// /// Order data by $priority. Note that this is used mainly for following filtering queries and due to firebase implementation /// the data may actually not be ordered. /// /// The child. /// The . public static OrderQuery OrderByPriority(this ChildQuery child) { return child.OrderBy("$priority"); } /// /// Instructs firebase to send data greater or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery StartAt(this ParameterQuery child, Func valueFactory) { return new FilterQuery(child, () => "startAt", valueFactory, child.Client); } /// /// Instructs firebase to send data lower or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EndAt(this ParameterQuery child, Func valueFactory) { return new FilterQuery(child, () => "endAt", valueFactory, child.Client); } /// /// Instructs firebase to send data equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EqualTo(this ParameterQuery child, Func valueFactory) { return new FilterQuery(child, () => "equalTo", valueFactory, child.Client); } /// /// Instructs firebase to send data greater or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery StartAt(this ParameterQuery child, Func valueFactory) { return new FilterQuery(child, () => "startAt", valueFactory, child.Client); } /// /// Instructs firebase to send data lower or equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EndAt(this ParameterQuery child, Func valueFactory) { return new FilterQuery(child, () => "endAt", valueFactory, child.Client); } /// /// Instructs firebase to send data equal to the . This must be preceded by an OrderBy query. /// /// Current node. /// Value to start at. /// The . public static FilterQuery EqualTo(this ParameterQuery child, Func valueFactory) { return new FilterQuery(child, () => "equalTo", valueFactory, child.Client); } /// /// Limits the result to first items. /// /// Current node. /// Number of elements. /// The . public static FilterQuery LimitToFirst(this ParameterQuery child, Func countFactory) { return new FilterQuery(child, () => "limitToFirst", () => countFactory(), child.Client); } /// /// Limits the result to last items. /// /// Current node. /// Number of elements. /// The . public static FilterQuery LimitToLast(this ParameterQuery child, Func countFactory) { return new FilterQuery(child, () => "limitToLast", () => countFactory(), child.Client); } } } ================================================ FILE: Database/Streaming/FirebaseCache.cs ================================================ namespace Firebase.Xamarin.Database.Streaming { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using Firebase.Xamarin.Database.Http; using Newtonsoft.Json; /// /// The firebase cache. /// /// Type of top-level entities in the cache. public class FirebaseCache : IEnumerable> { private readonly IDictionary dictionary; /// /// Initializes a new instance of the class. /// public FirebaseCache() : this(new Dictionary()) { } /// /// Initializes a new instance of the class and populates it with existing data. /// /// The existing items. public FirebaseCache(IDictionary existingItems) { this.dictionary = existingItems; } /// /// The push data. /// /// The path of incoming data, separated by slash. /// The data in json format as returned by firebase. /// Collection of top-level entities which were affected by the push. public IEnumerable> PushData(string path, string data) { object obj = this.dictionary; Action primitiveObjSetter = null; Action objDeleter = null; var pathElements = path.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries); // first find where we should insert the data to foreach (var element in pathElements) { if (obj is IDictionary) { // if it's a dictionary, then it's just a matter of inserting into it / accessing existing object by key var dictionary = obj as IDictionary; var valueType = obj.GetType().GenericTypeArguments[1]; primitiveObjSetter = (d) => dictionary[element] = d; objDeleter = () => dictionary.Remove(element); if (dictionary.Contains(element)) { obj = dictionary[element]; } else { if (valueType == typeof(string)) { dictionary[element] = string.Empty; obj = dictionary[element]; } else { dictionary[element] = Activator.CreateInstance(valueType); obj = dictionary[element]; } } } else { // if it's not a dictionary, try to find the property of current object with the matching name var objParent = obj; var property = objParent .GetType() .GetRuntimeProperties() .First(p => p.Name.Equals(element, StringComparison.OrdinalIgnoreCase) || element == p.GetCustomAttribute()?.PropertyName); objDeleter = () => property.SetValue(objParent, null); primitiveObjSetter = (d) => property.SetValue(objParent, d); obj = property.GetValue(obj); } } // if data is null (=empty string) delete it if (string.IsNullOrWhiteSpace(data)) { var key = pathElements[0]; var target = this.dictionary[key]; objDeleter(); yield return new FirebaseObject(key, target); yield break; } // now insert the data if (obj is IDictionary) { // insert data into dictionary and return it as a collection of FirebaseObject var dictionary = obj as IDictionary; var valueType = obj.GetType().GenericTypeArguments[1]; var objectCollection = data.GetObjectCollection(valueType); foreach (var item in objectCollection) { dictionary[item.Key] = item.Object; yield return new FirebaseObject(item.Key, (T)item.Object); } } else { // set the data on a property of the given object var valueType = obj.GetType(); var targetObject = JsonConvert.DeserializeObject(data, valueType); if ((valueType.GetTypeInfo().IsPrimitive || valueType == typeof(string)) && primitiveObjSetter != null) { // handle primitive (value) types separately primitiveObjSetter(targetObject); } else { JsonConvert.PopulateObject(data, obj); } this.dictionary[pathElements[0]] = this.dictionary[pathElements[0]]; yield return new FirebaseObject(pathElements[0], this.dictionary[pathElements[0]]); } } #region IEnumerable IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public IEnumerator> GetEnumerator() { return this.dictionary.Select(p => new FirebaseObject(p.Key, p.Value)).GetEnumerator(); } #endregion } } ================================================ FILE: Database/Streaming/FirebaseEvent.cs ================================================ namespace Firebase.Xamarin.Database.Streaming { /// /// Firebase event which hold and the object affected by the event. /// /// Type of object affected by the event. public class FirebaseEvent : FirebaseObject { /// /// Initializes a new instance of the class. /// /// The key of the object. /// The object. /// The event type. public FirebaseEvent(string key, T obj, FirebaseEventType eventType) : base(key, obj) { this.EventType = eventType; } /// /// Gets the event type. /// public FirebaseEventType EventType { get; } } } ================================================ FILE: Database/Streaming/FirebaseEventType.cs ================================================ namespace Firebase.Xamarin.Database.Streaming { /// /// The type of event. /// public enum FirebaseEventType { /// /// Item was inserted or updated. /// InsertOrUpdate, /// /// Item was deleted. /// Delete } } ================================================ FILE: Database/Streaming/FirebaseServerEventType.cs ================================================ namespace Firebase.Xamarin.Database.Streaming { internal enum FirebaseServerEventType { Put, Patch, KeepAlive, Cancel, AuthRevoked } } ================================================ FILE: Database/Streaming/FirebaseSubscription.cs ================================================ namespace Firebase.Xamarin.Database.Streaming { using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using Firebase.Xamarin.Database.Query; using Newtonsoft.Json.Linq; /// /// The firebase subscription. /// /// Type of object to be streaming back to the called. internal class FirebaseSubscription : IDisposable { private readonly CancellationTokenSource cancel; private readonly HttpClient httpClient; private readonly IObserver> observer; private readonly IFirebaseQuery query; private readonly FirebaseCache cache; private readonly string elementRoot; /// /// Initializes a new instance of the class. /// /// The observer. /// The query. /// The cache. public FirebaseSubscription(IObserver> observer, IFirebaseQuery query, string elementRoot, FirebaseCache cache) { this.observer = observer; this.query = query; this.elementRoot = elementRoot; this.cancel = new CancellationTokenSource(); this.httpClient = new HttpClient(); this.cache = cache; var handler = new HttpClientHandler { AllowAutoRedirect = true, MaxAutomaticRedirections = 10, }; this.httpClient = new HttpClient(handler, true) { Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite), }; this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream")); } public void Dispose() { this.httpClient.Dispose(); this.cancel.Cancel(); } public IDisposable Run() { Task.Factory.StartNew(this.ReceiveThread); return this; } private async void ReceiveThread() { while (true) { try { this.cancel.Token.ThrowIfCancellationRequested(); // initialize network connection var serverEvent = FirebaseServerEventType.KeepAlive; var request = new HttpRequestMessage(HttpMethod.Get, await this.query.BuildUrlAsync().ConfigureAwait(false)); var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, this.cancel.Token).ConfigureAwait(false); response.EnsureSuccessStatusCode(); using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var reader = new StreamReader(stream)) { while (true) { var line = reader.ReadLine(); this.cancel.Token.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(line)) { await Task.Delay(2000).ConfigureAwait(false); continue; } var tuple = line.Split(new[] { ':' }, 2, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); switch (tuple[0].ToLower()) { case "event": serverEvent = this.ParseServerEvent(serverEvent, tuple[1]); break; case "data": this.ProcessServerData(serverEvent, tuple[1]); break; } if (serverEvent == FirebaseServerEventType.AuthRevoked) { // auth token no longer valid, reconnect break; } } } } catch (OperationCanceledException) { break; } catch (Exception ex) { Debug.WriteLine("************************************************************"); Debug.WriteLine(ex.ToString()); Debug.WriteLine("************************************************************"); await Task.Delay(2000).ConfigureAwait(false); } } } private FirebaseServerEventType ParseServerEvent(FirebaseServerEventType serverEvent, string eventName) { switch (eventName) { case "put": serverEvent = FirebaseServerEventType.Put; break; case "patch": serverEvent = FirebaseServerEventType.Patch; break; case "keep-alive": serverEvent = FirebaseServerEventType.KeepAlive; break; case "cancel": serverEvent = FirebaseServerEventType.Cancel; break; case "auth_revoked": serverEvent = FirebaseServerEventType.AuthRevoked; break; } return serverEvent; } private void ProcessServerData(FirebaseServerEventType serverEvent, string serverData) { switch (serverEvent) { case FirebaseServerEventType.Put: case FirebaseServerEventType.Patch: var result = JObject.Parse(serverData); var path = result["path"].ToString(); var data = result["data"].ToString(); var eventType = string.IsNullOrWhiteSpace(data) ? FirebaseEventType.Delete : FirebaseEventType.InsertOrUpdate; var items = this.cache.PushData(this.elementRoot + path, data); foreach (var i in items.ToList()) { this.observer.OnNext(new FirebaseEvent(i.Key, i.Object, eventType)); } break; case FirebaseServerEventType.KeepAlive: break; case FirebaseServerEventType.Cancel: this.observer.OnError(new Exception("cancel")); this.Dispose(); throw new OperationCanceledException(); case FirebaseServerEventType.AuthRevoked: this.observer.OnError(new Exception("auth")); this.Dispose(); throw new OperationCanceledException(); } } } } ================================================ FILE: Firebase.Xamarin.csproj ================================================  10.0 Debug AnyCPU {4ED07DF1-95A7-4780-BC6A-BEF0BD4FF4C3} Library Properties Firebase.Xamarin Firebase.Xamarin en-US 512 {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Profile111 v4.5 true full false bin\Debug\ DEBUG;TRACE prompt 4 pdbonly true bin\Release\ TRACE prompt 4 packages\Rx-Core.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.Core.dll True packages\Rx-Interfaces.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.Interfaces.dll True packages\Rx-Linq.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.Linq.dll True packages\Rx-PlatformServices.2.2.5\lib\portable-net45+winrt45+wp8+wpa81\System.Reactive.PlatformServices.dll True packages\Portable.BouncyCastle.1.8.0\lib\portable-net45+win8+wpa81+MonoTouch10+MonoAndroid10+xamarinmac20+xamarinios10\crypto.dll packages\Portable.JWT.1.0.5\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\JWT.dll packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll ================================================ FILE: Firebase.Xamarin.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Firebase.Xamarin", "Firebase.Xamarin.csproj", "{4ED07DF1-95A7-4780-BC6A-BEF0BD4FF4C3}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Packager", "..\NuGet.Packager\NuGet.Packager.csproj", "{0451BAEF-DF2E-4B98-8644-94EE9415E389}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4ED07DF1-95A7-4780-BC6A-BEF0BD4FF4C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4ED07DF1-95A7-4780-BC6A-BEF0BD4FF4C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {4ED07DF1-95A7-4780-BC6A-BEF0BD4FF4C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {4ED07DF1-95A7-4780-BC6A-BEF0BD4FF4C3}.Release|Any CPU.Build.0 = Release|Any CPU {0451BAEF-DF2E-4B98-8644-94EE9415E389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0451BAEF-DF2E-4B98-8644-94EE9415E389}.Debug|Any CPU.Build.0 = Debug|Any CPU {0451BAEF-DF2E-4B98-8644-94EE9415E389}.Release|Any CPU.ActiveCfg = Release|Any CPU {0451BAEF-DF2E-4B98-8644-94EE9415E389}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 ricardo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Notification/PushNotification.cs ================================================ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Firebase.Xamarin.Auth; namespace Firebase.Xamarin.Notification { public class PushNotification { private const string NotificationUrl = "https://fcm.googleapis.com/fcm/send"; private readonly FirebaseConfig authConfig; public PushNotification(FirebaseConfig authConfig) { this.authConfig = authConfig; } public async Task Send(string to, string title, string message) { using (HttpClient client = new HttpClient()) { var postContent = $"{{\"to\":\"{to}\",\"notification\":{{ \"title\":\"{title}\",\"text\":\"{message}\" }}}}"; client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("key", $"={this.authConfig.ApiKeyForPushNotification}"); var response = await client.PostAsync(NotificationUrl, new StringContent(postContent, Encoding.UTF8, "application/json")).ConfigureAwait(false); response.EnsureSuccessStatusCode(); } } } } ================================================ FILE: Properties/AssemblyInfo.cs ================================================ using System.Resources; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Firebase.Xamarin")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Firebase.Xamarin")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: README.md ================================================ # Firebase.Xamarin Light weight wrapper for Firebase Realtime Database REST API. ## Installation ```csharp // Install release version Install-Package Firebase.Xamarin ``` ## Supported frameworks * .NET 4.5+ * ASP.Net Core 1.0 * Xamarin Android * Xamarin iOS * Xamarin iOS Classic * Windows 8 ## Usage Depending on your [database rules](https://firebase.google.com/docs/database/security/) you may need to first Authenticate with your Auth Provider. Instructions for Authentication are towards the bottom. ### Querying ```csharp var firebase = new FirebaseClient("https://yourdatabase.firebaseio.com/"); var items = await firebase .Child("yourentity") //.WithAuth("") // <-- Add Auth token if required. Auth instructions further down in readme. .OrderByKey() .LimitToFirst(2) .OnceAsync(); foreach (var item in items) { Console.WriteLine($"{item.Key} name is {item.Object.Name}"); } ``` ### Saving data ```csharp var firebase = new FirebaseClient("https://yourdatabase.firebaseio.com/"); // add new item to list of data var item = await firebase .Child("yourentity") //.WithAuth("") // <-- Add Auth token if required. Auth instructions further down in readme. .PostAsync(new YourObject()); // note that there is another overload for the PostAsync method which delegates the new key generation to the client Console.WriteLine($"Key for the new item: {item.Key}"); // add new item directly to the specified location (this will overwrite whatever data already exists at that location) var item = await firebase .Child("yourentity") .Child("Ricardo") //.WithAuth("") // <-- Add Auth token if required. Auth instructions further down in readme. .PutAsync(new YourObject()); ``` ### Realtime streaming ```csharp var firebase = new FirebaseClient("https://dinosaur-facts.firebaseio.com/"); var observable = firebase .Child("dinosaurs") .AsObservable() .Subscribe(d => Console.WriteLine(d.Key)); ``` ```AsObservable``` methods returns an ```IObservable``` which you can take advantage of using [Reactive Extensions](https://github.com/Reactive-Extensions/Rx.NET) ## Authentication ### firebase.google.com Auth You will need a firebase.google.com API Key for Authentication. The easiset way to find this is to click on 'Add Firebase to your web app' in the Overview section of your firebase.google.com console. The site will generate a JavaScript snippet that contains the ```apiKey``` variable. ```csharp // Email/Password Auth var authProvider = new FirebaseAuthProvider(new FirebaseConfig("")); var auth = await authProvider.CreateUserWithEmailAndPasswordAsync("email@email.com", "password"); // The auth Object will contain auth.User and the Authentication Token from the request System.Diagnostics.Debug.WriteLine(auth.FirebaseToken); // Facebook Auth var authProvider = new FirebaseAuthProvider(new FirebaseConfig("")); var facebookAccessToken = ""; var auth = await authProvider.SignInWithOAuthAsync(FirebaseAuthType.Facebook, facebookAccessToken); // Using the Auth token to make requests.. (see more on requests below) var firebase = new FirebaseClient("https://dinosaur-facts.firebaseio.com/"); var dinos = await firebase .Child("dinosaurs") .WithAuth(auth.FirebaseToken) // <-- Note the use of the Firebase Auth Token .OnceAsync(); foreach (var dino in dinos) { System.Diagnostics.Debug.WriteLine($"{dino.Key} is {dino.Object.Height}m high."); } ``` ### Custom Auth #### Generating Tokens To generate tokens, you'll need your Firebase Secret which you can find by entering your Firebase URL into a browser and clicking the "Secrets" tab on the left-hand navigation menu. Once you've downloaded the library and grabbed your Firebase Secret, you can generate a token with this snippet of .Net code: ``` var tokenGenerator = new Firebase.TokenGenerator(""); var authPayload = new Dictionary() { { "uid", "1" }, { "some", "arbitrary" }, { "data", "here" } }; string token = tokenGenerator.CreateToken(authPayload); ``` The payload object passed into `CreateToken()` is then available for use within your security rules via the [`auth` variable](https://www.firebase.com/docs/security/api/rule/auth.html). This is how you pass trusted authentication details (e.g. the client's user ID) to your Firebase rules. The payload can contain any data of your choosing, however it must contain a "uid" key, which must be a string of less than 256 characters. The generated token must be less than 1024 characters in total. #### Token Options A second `options` argument can be passed to `CreateToken()` to modify how Firebase treats the token. Available options are: * **expires** (DateTime) - A timestamp denoting the time after which this token should no longer be valid. * **notBefore** (DateTime) - A timestamp denoting the time before which this token should be rejected by the server. * **admin** (bool) - Set to `true` if you want to disable all security rules for this client. This will provide the client with read and write access to your entire Firebase. * **debug** (bool) - Set to `true` to enable debug output from your security rules. You should generally *not* leave this set to `true` in production (as it slows down the rules implementation and gives your users visibility into your rules), but it can be helpful for debugging. Here is an example of how to use the second `options` argument: ``` var tokenGenerator = new Firebase.TokenGenerator(""); var authPayload = new Dictionary() { { "uid", "1" }, { "some", "arbitrary" }, { "data", "here" } }; string token = tokenGenerator.CreateToken(authPayload, new Firebase.TokenOptions(admin: true)); ``` ## Thanks Special thanks to [bezysoftware](https://github.com/bezysoftware) for the original [firebase-database-dotnet] (https://github.com/step-up-labs/firebase-database-dotnet) and [firebase-authentication-dotnet] (https://github.com/step-up-labs/firebase-authentication-dotnet) code that is the core for this Xamarin adaptation. Also thanks to [mikelehen](https://github.com/mikelehen) for the original [Firebase Token Generator - .NET] (https://github.com/firebase/firebase-token-generator-dotNet) ================================================ FILE: Token/StreamToken.cs ================================================ namespace Firebase.Xamarin.Token { using System; using System.Reactive.Linq; using Firebase.Xamarin.Database.Streaming; public class StreamToken : IDisposable { private IDisposable _observableDisposable; private IObservable> _observable; public StreamToken(IObservable> observable) { _observable = observable; } public StreamToken Where(Func, bool> predicate) { _observable = _observable.Where(predicate); return this; } public StreamToken Throttle(TimeSpan timeSpan) { _observable = _observable.Throttle(timeSpan); return this; } public void Subscribe(Action> onDataAdded) => _observableDisposable = _observable.Subscribe(d => onDataAdded(d)); public void Dispose() { _observableDisposable?.Dispose(); } } } ================================================ FILE: Token/TokenGenerator.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace Firebase.Xamarin.Token { public class TokenGenerator { private static int TOKEN_VERSION = 0; private string _firebaseSecret; /// /// Constructor. /// /// The Firebase Secret for your firebase (can be found by entering your Firebase URL into a web browser, and clicking the "Auth" pane). public TokenGenerator(string firebaseSecret) { _firebaseSecret = firebaseSecret; } /// /// Creates an authentication token containing arbitrary auth data. /// /// Arbitrary data that will be passed to the Firebase Rules API, once a client authenticates. Must be able to be serialized to JSON with . /// The auth token. public string CreateToken(Dictionary data) { return CreateToken(data, new TokenOptions()); } /// /// Creates an authentication token containing arbitrary auth data and the specified options. /// /// Arbitrary data that will be passed to the Firebase Rules API, once a client authenticates. Must be able to be serialized to JSON with . /// A set of custom options for the token. /// The auth token. public string CreateToken(Dictionary data, TokenOptions options) { var dataEmpty = (data == null || data.Count == 0); if (dataEmpty && (options == null || (!options.admin && !options.debug))) { throw new Exception("data is empty and no options are set. This token will have no effect on Firebase."); } var claims = new Dictionary(); claims["v"] = TOKEN_VERSION; claims["iat"] = secondsSinceEpoch(DateTime.Now); var isAdminToken = (options != null && options.admin); validateToken(data, isAdminToken); if (!dataEmpty) { claims["d"] = data; } // Handle options. if (options != null) { if (options.expires.HasValue) claims["exp"] = secondsSinceEpoch(options.expires.Value); if (options.notBefore.HasValue) claims["nbf"] = secondsSinceEpoch(options.notBefore.Value); if (options.admin) claims["admin"] = true; if (options.debug) claims["debug"] = true; } var token = computeToken(claims); if (token.Length > 1024) { throw new Exception("Generated token is too long. The token cannot be longer than 1024 bytes."); } return token; } private string computeToken(Dictionary claims) { return JWT.JsonWebToken.Encode(claims, this._firebaseSecret, JWT.JwtHashAlgorithm.HS256); } private static long secondsSinceEpoch(DateTime dt) { TimeSpan t = dt.ToUniversalTime() - new DateTime(1970, 1, 1); return (long)t.TotalSeconds; } private static void validateToken(Dictionary data, Boolean isAdminToken) { var containsUid = (data != null && data.ContainsKey("uid")); if ((!containsUid && !isAdminToken) || (containsUid && !(data["uid"] is string))) { throw new Exception("Data payload must contain a \"uid\" key that must not be a string."); } else if (containsUid && data["uid"].ToString().Length > 256) { throw new Exception("Data payload must contain a \"uid\" key that must not be longer than 256 characters."); } } } } ================================================ FILE: Token/TokenOptions.cs ================================================ using System; namespace Firebase.Xamarin.Token { public class TokenOptions { public DateTime? expires { get; private set; } public DateTime? notBefore { get; private set; } public bool admin { get; private set; } public bool debug { get; private set; } /// /// Constructor. All options are optional. /// /// The date/time before which the token should not be considered valid. (default is now) /// The date/time at which the token should no longer be considered valid. (default is 24 hours from now) /// Set to true to bypass all security rules. (you can use this for trusted server code) /// Set to true to enable debug mode. (so you can see the results of Rules API operations) public TokenOptions(DateTime? notBefore = null, DateTime? expires = null, bool admin = false, bool debug = false) { this.notBefore = notBefore; this.expires = expires; this.admin = admin; this.debug = debug; } } } ================================================ FILE: TokenGenerator.config ================================================  ================================================ FILE: packages.config ================================================