├── database ├── images │ ├── 1.png │ └── 2.png └── README.md ├── auth ├── facebook │ ├── images │ │ ├── 1.png │ │ └── 2.png │ └── README.md ├── twitter │ ├── images │ │ └── 1.png │ └── README.md ├── README.md └── email │ └── README.md ├── LICENSE ├── utils └── Responses.as ├── examples ├── UploadWithProgress.mxml ├── README.md ├── FederatedLogin.mxml ├── SimpleCRUD.mxml ├── EmailLogin.mxml ├── FederatedCRUD.mxml ├── SimpleChat.mxml └── FileManager.mxml ├── README.md └── storage └── README.md /database/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/firebase-as3/HEAD/database/images/1.png -------------------------------------------------------------------------------- /database/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/firebase-as3/HEAD/database/images/2.png -------------------------------------------------------------------------------- /auth/facebook/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/firebase-as3/HEAD/auth/facebook/images/1.png -------------------------------------------------------------------------------- /auth/facebook/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/firebase-as3/HEAD/auth/facebook/images/2.png -------------------------------------------------------------------------------- /auth/twitter/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/firebase-as3/HEAD/auth/twitter/images/1.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Phantom App Development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /auth/twitter/README.md: -------------------------------------------------------------------------------- 1 | # Twitter 2 | 3 | This guide will help you configure Twitter for your Firebase Auth app. 4 | 5 | You will be required to provide a redirect URI, the URI should be similar to: `https://my-app-12345.firebaseapp.com/__/auth/handler` 6 | 7 | You only need to change the project name in the beginning of the URI. 8 | 9 | ## Configuring 10 | 11 | 1. Register in the [Twitter Developer portal](https://dev.twitter.com). You may be asked to provide a valid cellphone number to complete your registration. 12 | 13 | 2. Once registered locate a button that says `Create New App` and click it. 14 | 15 | 3. Fill all the fields as normally. Once you reach the Callback URL, provide the URL previously mentioned. 16 | 17 | ![Correct Settings](./images/1.png) 18 | 19 | 4. Go to the Settings tab and make sure these two options are checked: 20 | 21 | * Enable Callback Locking (It is recommended to enable callback locking to ensure apps cannot overwrite the callback url). 22 | * Allow this application to be used to Sign in with Twitter. 23 | 24 | 5. Go to the `Keys and Access Tokens` tab and copy down your `Consumer Key` and `Consumer Secret`. 25 | 6. Go back to the [Firebase console](https://firebase.google.com) and select your project. 26 | 7. Click the `Auth` option in the left side menu. 27 | 8. Click the `SIGN-IN METHOD` button in the top menu and then select `Twitter` from the providers list. 28 | 9. Click the `Enable` toggle button and set it to `on`, you will be asked for the `API Key` and `API Secret` which are the `Consumer Key` and `Consumer Secret` respectively. 29 | 10. Once you have finished filling the form press the `Save` button. 30 | 31 | The Twitter provider has been successfully enabled and configured. -------------------------------------------------------------------------------- /utils/Responses.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | /* 4 | 5 | This class contains all the error responses the Firebase API may output. 6 | These responses have been taken from the Firebase 3.4.0 JavaScript SDK. 7 | The response strings have been modified for clearer readibility. 8 | 9 | To use this class, you need to convert a Firebase JSON response into an AS3 Object using the JSON.parse() method 10 | 11 | var response:Object = JSON.parse(event.currentTarget.data); 12 | Alert.show(Responses[response.error.message], "Error"); 13 | 14 | */ 15 | public class Responses extends Object 16 | { 17 | Responses["INVALID_CUSTOM_TOKEN"] = "Invalid Custom Token"; 18 | Responses["CREDENTIAL_MISMATCH"] = "Custom Token Mismatch"; 19 | Responses["MISSING_CUSTOM_TOKEN"] = "Missing Custom Token"; 20 | Responses["INVALID_IDENTIFIER"] = "Invalid Email"; 21 | Responses["MISSING_CONTINUE_URI"] = "Missing Continue URI"; 22 | Responses["INVALID_EMAIL"] = "Invalid Email"; 23 | Responses["INVALID_PASSWORD"] = "Wrong Password"; 24 | Responses["USER_DISABLED"] = "User Disabled"; 25 | Responses["MISSING_PASSWORD"] = "Missing Password"; 26 | Responses["EMAIL_EXISTS"] = "Email Already in Use"; 27 | Responses["PASSWORD_LOGIN_DISABLED"] = "Operation Not Allowed"; 28 | Responses["INVALID_IDP_RESPONSE"] = "Invalid Credential"; 29 | Responses["FEDERATED_USER_ID_ALREADY_LINKED"] = "Credential Already in Use"; 30 | Responses["EMAIL_NOT_FOUND"] = "User Not Found"; 31 | Responses["EXPIRED_OOB_CODE"] = "Expired Action Code"; 32 | Responses["INVALID_OOB_CODE"] = "Invalid Action Code"; 33 | Responses["MISSING_OOB_CODE"] = "Missing Action Code"; 34 | Responses["CREDENTIAL_TOO_OLD_LOGIN_AGAIN"] = "Requires Recent Login"; 35 | Responses["INVALID_ID_TOKEN"] = "Invalid User Token"; 36 | Responses["TOKEN_EXPIRED"] = "User Token Expired"; 37 | Responses["USER_NOT_FOUND"] = "User Token Expired"; 38 | Responses["CORS_UNSUPPORTED"] = "Cross-Origin Resource Sharing Unsupported"; 39 | Responses["TOO_MANY_ATTEMPTS_TRY_LATER"] = "Too Many Attempts, Try Later"; 40 | Responses["WEAK_PASSWORD"] = "Weak Password"; 41 | Responses["OPERATION_NOT_ALLOWED"] = "Operation Not Allowed"; 42 | Responses["USER_CANCELLED"] = "User Cancelled" 43 | } 44 | } -------------------------------------------------------------------------------- /auth/facebook/README.md: -------------------------------------------------------------------------------- 1 | # Facebook 2 | 3 | This guide will help you configure Facebook for your Firebase Auth app. 4 | 5 | You will be required to provide a redirect URI, the URI should be similar to: `https://my-app-12345.firebaseapp.com/__/auth/handler` 6 | 7 | You only need to change the project name in the beginning of the URI. 8 | 9 | ## Configuring 10 | 11 | 1. Register in the [Facebook Developer portal](https://developers.facebook.com/). You may be asked to provide a valid cell phone number to complete your registration. 12 | 2. Once registered, locate a drop-down list in the top right and click on it. At the middle it will be an option to `Add a New App`. 13 | 3. You will be presented with 4 options, select `Website` (even if you are developing an Android or iOS app). We can later add the other platforms when we are ready for deployment. 14 | 4. Click the button that says `Skip and Create App ID`. A form will appear where you have to provide your app details, once finished click the `Create App ID` button. 15 | 16 | ![Correct Settings](./images/1.png) 17 | 18 | 5. You will be redirected to your `Dashboard`, click the `Get Started` button next to the `Facebook Login` option. 19 | 6. Configure the OAuth settings similar to the next image. Remember to set the correct redirect URI and press the `Save Changes` button. 20 | 21 | ![Correct Settings](./images/2.png) 22 | 23 | 7. Click `Settings` in the left side menu. You will be presented with a screen with your `App ID` and `App Secret`, you will be required to provide your Facebook password to see the `App Secret`. 24 | 8. Once authorized, copy down your `App ID` and `App Secret`. We are going to use them in a later step. 25 | 26 | At this point the Facebook API will only work with your own Facebook Account, in order for it to work on any account you need to switch your app status from `development` to `public`. 27 | 28 | 9. If you are still in the `Dashboard` click the `App Review` option in the left side menu. 29 | 30 | 10. In the `Make [Your App Name] public?` section turn it to `Yes`. A pop-up will appear asking if you want to make your app public, click the `Confirm` button. 31 | 32 | With these settings your app will only be allowed to do reading operations with the Facebook API. If you want to do writing operations such as `Liking` and posting to the user wall you must submit your app for review. 33 | 34 | 11. Go back to the [Firebase console](https://firebase.google.com) and select your project. 35 | 12. Click the `Auth` option in the left side menu. 36 | 13. Click the `SIGN-IN METHOD` button in the top menu and then select `Facebook` from the providers list. 37 | 14. Click the `Enable` toggle button and set it to `on`, you will be asked for the `App ID` and `App Secret`. 38 | 15. Once you have finished filling the form press the `Save` button. 39 | 40 | The Facebook provider has been successfully enabled and configured. -------------------------------------------------------------------------------- /examples/UploadWithProgress.mxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | .appspot.com/o/public_files%2F"; 11 | private var fileRef:FileReference; 12 | 13 | /* 14 | This script uploads any file from your computer directly to Firebase Storage and displays a progressbar indicating upload progress. 15 | The assigment of EventListeners is super important. IT is advised to not modify the control flow and just copy and paste the code as-is. 16 | */ 17 | private function uploadFile(event:MouseEvent):void 18 | { 19 | fileRef = new FileReference(); 20 | fileRef.addEventListener(Event.SELECT, selectHandler); 21 | fileRef.addEventListener(Event.COMPLETE, completeHandler); 22 | fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA,uploadCompleteDataHandler); 23 | fileRef.browse(); 24 | } 25 | 26 | /* 27 | The file has been selected and it will now be loaded into memory. 28 | */ 29 | private function selectHandler(event:Event):void 30 | { 31 | fileRef.load(); 32 | } 33 | 34 | /* 35 | We prepare the URLRequest that will be sent in the upload method. 36 | The file will be sent as a String that represents the bytes from the file. 37 | We are using uploadUnencoded instead of upload since we have already encoded our data by sending it as a String. 38 | Finally, we tell the PopUpManager to add our custom popup and center it. 39 | */ 40 | private function completeHandler(event:Event):void 41 | { 42 | fileRef.removeEventListener(Event.COMPLETE, completeHandler); 43 | fileRef.addEventListener(ProgressEvent.PROGRESS, progressHandler); 44 | 45 | var request:URLRequest = new URLRequest(FIREBASE_STORAGE_URL+fileRef.name); 46 | request.method = URLRequestMethod.POST; 47 | request.data = fileRef.data.toString(); 48 | request.contentType = "image/jpeg"; 49 | 50 | fileRef.uploadUnencoded(request); 51 | PopUpManager.addPopUp(myPopUp, this, true); 52 | PopUpManager.centerPopUp(myPopUp); 53 | } 54 | 55 | /* 56 | This event will be fired everytime the server reports progress back. 57 | We update the label and the progress of the ProgressBar with the new status from bytesLoaded. 58 | */ 59 | private function progressHandler(event:ProgressEvent):void 60 | { 61 | var progress:Number = Math.round((event.bytesLoaded/event.bytesTotal)*100); 62 | 63 | myProgressBar.setProgress(progress, 100); 64 | myProgressBar.label = "Uploading Progress: " + progress + "%"; 65 | } 66 | 67 | /* 68 | Once our file has been uploaded we will receive the JSON response from Firebase. 69 | We pass the JSON string into a TextArea and remove the popup. 70 | */ 71 | private function uploadCompleteDataHandler(event:DataEvent):void 72 | { 73 | PopUpManager.removePopUp(myPopUp); 74 | myTextArea.text = event.data.toString(); 75 | } 76 | 77 | ]]> 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | In this folder you will find several examples on how to use the Firebase services with ActionScript 3. 4 | 5 | It is strongly recommended to use recent versions of `Adobe AIR` and `Apache Flex`. 6 | 7 | ## SimpleChat.mxml 8 | 9 | An Apache Flex example that demonstrates how to use the Firebase Database with realtime data and Email auth. 10 | 11 | You will require to enable the `Email` provider for your project, you will also require the following Database Rules: 12 | 13 | ```json 14 | { 15 | "rules": { 16 | "messages": { 17 | ".read": "auth != null", 18 | ".write": "auth != null" 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | ## SimpleCRUD.mxml 25 | 26 | An Apache Flex example that demonstrates how to use the Firebase Database with non realtime data. 27 | 28 | You will only require the following Database Rules: 29 | 30 | ```json 31 | { 32 | "rules": { 33 | "journal": { 34 | ".read": "true", 35 | ".write": "true" 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | ## FederatedCRUD.mxml 42 | 43 | An Apache Flex example that demonstrates how to use Federated Login with the Firebase Database to manage a private journal. Each user can only read and modify their own journal. 44 | 45 | You will require to enable the `Facebook`, `Twitter` or `Google` providers for your project, you will also require the following Database Rules: 46 | 47 | ```json 48 | { 49 | "rules": { 50 | "journal": { 51 | "$user_id": { 52 | ".read": "$user_id === auth.uid", 53 | ".write": "$user_id === auth.uid" 54 | } 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | ## FileManager.mxml 61 | 62 | An Apache Flex example that demonstrates how to use Firebase Auth, Firebase Storage and Firebase Database to store and manage user images. 63 | Every user will have their own private folder where they will be able to upload, download and delete their images. 64 | 65 | You will require to enable the `Email` provider for your project, you will also require the following Database Rules: 66 | 67 | ```json 68 | { 69 | "rules": { 70 | "images": { 71 | "$user_id": { 72 | ".read": "$user_id === auth.uid", 73 | ".write": "$user_id === auth.uid" 74 | } 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | You will require the following Storage Rules: 81 | 82 | ``` 83 | service firebase.storage { 84 | match /b/.appspot.com/o { 85 | match /images/{userId}/{allPaths=**} { 86 | allow read, write: if request.auth.uid == userId; 87 | } 88 | } 89 | } 90 | ``` 91 | 92 | ## UploadWithProgress.mxml 93 | 94 | An Apache Flex example that demonstrates how to use Firebase Storage to store any kind of files and show a progress indicator. 95 | 96 | You will require the following Storage Rules: 97 | 98 | ``` 99 | service firebase.storage { 100 | match /b/.appspot.com/o { 101 | match /public_files/{allPaths=**} { 102 | allow read, write; 103 | } 104 | } 105 | } 106 | ``` 107 | 108 | ## EmailLogin.mxml 109 | 110 | An Apache Flex example that demonstrates how to perform most operations from the Email Auth service. 111 | 112 | You will only require to provide your Firebase API Key and enable the `Email & Password` auth provider. 113 | 114 | ## FederatedLogin.mxml 115 | 116 | An Apache Flex example that demonstrates how to perform log-in using Google, Twitter and Facebook providers within the same app. 117 | 118 | You will only require to provide your Firebase API Key and enable the providers of your choice. 119 | 120 | ## ToDo App 121 | *Main repository: [ToDo App](https://github.com/PhantomAppDevelopment/todo-app)* 122 | 123 | ToDo App is a mobile application developed with Starling Framework and FeathersUI. It showcases how to use Firebase services with ActionScript to create simple and secure CRUD system. 124 | 125 | ## Pizza App 126 | *Main repository: [Pizza App](https://github.com/PhantomAppDevelopment/pizza-app)* 127 | 128 | Pizza App is a mobile application developed with Starling Framework and FeathersUI. It showcases how to use Firebase services with ActionScript to create a small social network. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase in ActionScript 2 | 3 | Firebase is a back end platform that offers several services to aid in the development of apps and games, specially the ones that rely on server side infrastructure. 4 | 5 | Some of its services can be accessed by using a RESTful approach. This repository contains detailed guides and [examples](./examples) explaining how to use those services in your `Adobe AIR ` projects. 6 | 7 | You won't need an `ANE` for these guides, all of them work only using `StageWebView`, `URLRequest` and `URLLoader`. 8 | 9 | ## Firebase Auth 10 | *Main guide: [Firebase Auth](./auth)* 11 | 12 | This service allows you to securely authenticate users into your app. It uses Google Identity Toolkit to provide this service. Some of its key features are: 13 | 14 | * Leverages the use of OAuth, saving time and effort. 15 | * Authenticate with `Facebook`, `Google`, `Twitter`, `Email`, `Anonymous` and more. 16 | * Generates an `authToken` that can be used for secure operations against Firebase Storage and Firebase Database. 17 | 18 | ## Firebase Database 19 | *Main guide: [Firebase Database](./database)* 20 | 21 | This service allows you to save and retrieve text based data. Some of its key features are: 22 | 23 | * Securely save and retrieve data using rules and Firebase Auth. 24 | * Listen to changes in realtime, useful for chat based apps. 25 | * Data is generated in JSON, making it lightweight and fast to load. 26 | * Easy to model and understand data structures. 27 | * Filter, organize and query the data. 28 | 29 | ## Firebase Storage 30 | *Main guide: [Firebase Storage](./storage)* 31 | 32 | This service allows you to upload and maintain all kinds of files, including images, sounds, videos and binaries. It uses Google Cloud Storage to provide this service. Some of its key features are: 33 | 34 | * Securely save, retrieve and delete files using rules and Firebase Auth. 35 | * Load end edit metadata from files. 36 | 37 | ## Getting Started 38 | 39 | This guide assumes you want to use the 3 services in the same application, you will be able to use them with a free account. 40 | 41 | Before you start coding you need to follow these steps to prepare your application for Firebase: 42 | 43 | 1. Create or open a project in the [Firebase Console](https://firebase.google.com) 44 | 2. You will be presented with 3 options for adding your app to `iOS`, `Android` or `Web`. 45 | 3. Click `Web`, a popup will appear with information about your project. Copy down your `apiKey` and `authDomain`. 46 | 47 | From the `authDomain` we only need the id of the project, an example of an id is: `my-app-12345`. 48 | 49 | You can read the guides in any order but it is recommended to start with the [Firebase Auth guide](./auth). 50 | 51 | ## FAQs 52 | 53 | ### **What is the difference between these guides and existing Firebase ANEs?** 54 | 55 | Firebase ANEs are based on the Android and iOS official SDKs, providing all of their native features. 56 | 57 | These guides are based on the JavaScript SDK, which provides the same functionality from the Web browser but inside the AIR runtime. 58 | 59 | ### **Which are the benefits of using these guides?** 60 | 61 | These guides work on Android, iOS, Windows and OSX using only the ActionScript 3 standard library, this will greatly reduce time when reusing your implementation code for all 4 platforms. 62 | 63 | You won't need to embed several ANEs to your project, this is very important for developers who are concerned with the app size. 64 | 65 | They are also a great way to understand how Firebase works behind the scenes. 66 | 67 | Free and open source! 68 | 69 | ### **Why only Database, Auth and Storage, what about the other Firebase features?** 70 | 71 | These guides are based on the JavaScript SDK and therefore have their same limitation of being web based only. If you need the rest of features that Firebase offers I strongly recommend using an ANE. 72 | 73 | ### **How did you got the documentation for Auth and Storage?** 74 | 75 | I studied the JavaScript SDK and its official documentation, then I determined the API paths, requests, results and errors. 76 | 77 | ### **What about Flash Player projects?** 78 | 79 | For Flash Player projects I recommend using the `ExternalInterface` class with the official JavaScript SDK. 80 | 81 | ## Donations 82 | 83 | Feel free to support the development of free guides and examples. Your donations are greatly appreciated. 84 | 85 | [![Become a Patron!](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=20521425) 86 | -------------------------------------------------------------------------------- /examples/FederatedLogin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | .firebaseapp.com/__/auth/handler"; 12 | private var webView:StageWebView; 13 | private var sessionId:String; 14 | private var requestUri:String; 15 | 16 | private function startAuth(provider:String):void 17 | { 18 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 19 | 20 | var myObject:Object = new Object(); 21 | myObject.continueUri = FIREBASE_REDIRECT_URL; 22 | myObject.providerId = provider; 23 | 24 | var request:URLRequest = new URLRequest(FIREBASE_CREATE_AUTH_URL); 25 | request.method = URLRequestMethod.POST; 26 | request.data = JSON.stringify(myObject); 27 | request.requestHeaders.push(header); 28 | 29 | var loader:URLLoader = new URLLoader(); 30 | loader.addEventListener(flash.events.Event.COMPLETE, authURLCreated); 31 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 32 | loader.load(request); 33 | } 34 | 35 | private function authURLCreated(event:flash.events.Event):void 36 | { 37 | var rawData:Object = JSON.parse(event.currentTarget.data); 38 | 39 | //We store the sessionId value from the response for later use 40 | sessionId = rawData.sessionId; 41 | 42 | webView = new StageWebView(); 43 | webView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, changeLocation); 44 | webView.stage = this.stage; 45 | webView.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); 46 | 47 | //We load the URL from the response, it will automatically contain the client id, scopes and the redirect URL 48 | webView.loadURL(rawData.authUri); 49 | } 50 | 51 | private function changeLocation(event:LocationChangeEvent):void 52 | { 53 | var location:String = webView.location; 54 | 55 | if(location.indexOf("/__/auth/handler?code=") != -1 || location.indexOf("/__/auth/handler?state=") != -1 || location.indexOf("/__/auth/handler#state=") != -1 && location.indexOf("error") == -1){ 56 | 57 | //We are looking for a code parameter in the URL, once we have it we dispose the webview and prepare the last URLRequest 58 | webView.removeEventListener(LocationChangeEvent.LOCATION_CHANGE, changeLocation); 59 | webView.dispose(); 60 | 61 | requestUri = location; 62 | getAccountInfo(); 63 | } 64 | } 65 | 66 | private function getAccountInfo():void 67 | { 68 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 69 | 70 | var myObject:Object = new Object(); 71 | myObject.requestUri = requestUri; 72 | myObject.sessionId = sessionId; 73 | myObject.returnSecureToken = true; 74 | 75 | var request:URLRequest = new URLRequest(FIREBASE_VERIFY_ASSERTION_URL); 76 | request.method = URLRequestMethod.POST; 77 | request.data = JSON.stringify(myObject); 78 | request.requestHeaders.push(header); 79 | 80 | var loader:URLLoader = new URLLoader(); 81 | loader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 82 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 83 | loader.load(request); 84 | } 85 | 86 | private function registerComplete(event:flash.events.Event):void 87 | { 88 | currentState = "InfoState"; 89 | myTA.text = event.currentTarget.data; 90 | } 91 | 92 | private function errorHandler(event:flash.events.IOErrorEvent):void 93 | { 94 | trace(event.currentTarget.data); 95 | } 96 | 97 | ]]> 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /examples/SimpleCRUD.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | .firebaseio.com/journal"; 12 | 13 | protected function init(event:FlexEvent):void 14 | { 15 | loadJournal(); 16 | } 17 | 18 | protected function selectItem(event:GridSelectionEvent):void 19 | { 20 | titleInput.text = journalGrid.selectedItem.title; 21 | descriptionInput.text = journalGrid.selectedItem.description; 22 | 23 | deleteBtn.enabled = true; 24 | modifyBtn.enabled = true; 25 | } 26 | 27 | /* 28 | Read block 29 | */ 30 | private function loadJournal():void 31 | { 32 | var request:URLRequest = new URLRequest(JOURNAL_URL+".json"); 33 | 34 | var loader:URLLoader = new URLLoader(); 35 | loader.addEventListener(flash.events.Event.COMPLETE, journalLoaded); 36 | loader.load(request); 37 | } 38 | 39 | private function journalLoaded(event:flash.events.Event):void 40 | { 41 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, journalLoaded); 42 | 43 | //The JSON generated by Firebase contains the id as the node key, we use this function to add it to our Objects 44 | var rawData:Object = JSON.parse(event.currentTarget.data); 45 | var entriesArray:Array = new Array(); 46 | 47 | for (var parent:String in rawData) 48 | { 49 | var tempObject:Object = new Object(); 50 | tempObject.id = parent; 51 | 52 | for (var child:* in rawData[parent]) 53 | { 54 | tempObject[child] = rawData[parent][child]; 55 | } 56 | 57 | entriesArray.push(tempObject); 58 | tempObject = null; 59 | } 60 | 61 | journalGrid.dataProvider = new ArrayList(entriesArray); 62 | 63 | titleInput.text = ""; 64 | descriptionInput.text = ""; 65 | deleteBtn.enabled = false; 66 | modifyBtn.enabled = false; 67 | } 68 | 69 | /* 70 | Insert block 71 | */ 72 | private function saveEntry():void 73 | { 74 | var myObject:Object = new Object(); 75 | myObject.title = titleInput.text; 76 | myObject.description = descriptionInput.text; 77 | myObject.timestamp = new Date().getTime(); 78 | 79 | var request:URLRequest = new URLRequest(JOURNAL_URL+".json"); 80 | request.data = JSON.stringify(myObject); 81 | request.method = URLRequestMethod.POST; 82 | 83 | var loader:URLLoader = new URLLoader(); 84 | loader.addEventListener(flash.events.Event.COMPLETE, entrySent); 85 | loader.load(request); 86 | } 87 | 88 | private function entrySent(event:flash.events.Event):void 89 | { 90 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, entrySent); 91 | trace(event.currentTarget.data); 92 | loadJournal(); 93 | } 94 | 95 | /* 96 | Delete block 97 | */ 98 | private function deleteEntry():void 99 | { 100 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 101 | 102 | var request:URLRequest = new URLRequest(JOURNAL_URL+"/"+journalGrid.selectedItem.id+".json"); 103 | request.method = URLRequestMethod.POST; 104 | request.requestHeaders.push(header); 105 | 106 | var loader:URLLoader = new URLLoader(); 107 | loader.addEventListener(flash.events.Event.COMPLETE, entryDeleted); 108 | loader.load(request); 109 | } 110 | 111 | private function entryDeleted(event:flash.events.Event):void 112 | { 113 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, entryDeleted); 114 | trace(event.currentTarget.data); 115 | loadJournal(); 116 | } 117 | 118 | /* 119 | Update block 120 | */ 121 | private function updateEntry():void 122 | { 123 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 124 | 125 | var myObject:Object = new Object(); 126 | myObject.title = titleInput.text; 127 | myObject.description = descriptionInput.text; 128 | 129 | var request:URLRequest = new URLRequest(JOURNAL_URL+"/"+journalGrid.selectedItem.id+".json"); 130 | request.data = JSON.stringify(myObject); 131 | request.method = URLRequestMethod.POST; 132 | request.requestHeaders.push(header); 133 | 134 | var loader:URLLoader = new URLLoader(); 135 | loader.addEventListener(flash.events.Event.COMPLETE, entryUpdated); 136 | loader.load(request); 137 | } 138 | 139 | private function entryUpdated(event:flash.events.Event):void 140 | { 141 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, entryUpdated); 142 | trace(event.currentTarget.data); 143 | loadJournal(); 144 | } 145 | 146 | ]]> 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /examples/EmailLogin.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /examples/FederatedCRUD.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | .firebaseapp.com/__/auth/handler"; 13 | private static const JOURNAL_URL:String = "https://.firebaseio.com/journal/"; 14 | 15 | private var webView:StageWebView; 16 | private var sessionId:String; 17 | private var requestUri:String; 18 | private var profile:Object; 19 | private var authToken:String; 20 | 21 | private function startAuth(provider:String):void 22 | { 23 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 24 | 25 | var myObject:Object = new Object(); 26 | myObject.continueUri = FIREBASE_REDIRECT_URL; 27 | myObject.providerId = provider; 28 | 29 | var request:URLRequest = new URLRequest(FIREBASE_CREATE_AUTH_URL); 30 | request.method = URLRequestMethod.POST; 31 | request.data = JSON.stringify(myObject); 32 | request.requestHeaders.push(header); 33 | 34 | var loader:URLLoader = new URLLoader(); 35 | loader.addEventListener(flash.events.Event.COMPLETE, authURLCreated); 36 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 37 | loader.load(request); 38 | } 39 | 40 | private function authURLCreated(event:flash.events.Event):void 41 | { 42 | var rawData:Object = JSON.parse(event.currentTarget.data); 43 | 44 | //We store the sessionId value from the response for later use 45 | sessionId = rawData.sessionId; 46 | 47 | webView = new StageWebView(); 48 | webView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, changeLocation); 49 | webView.stage = this.stage; 50 | webView.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); 51 | 52 | //We load the URL from the response, it will automatically contain the client id, scopes and the redirect URL 53 | webView.loadURL(rawData.authUri); 54 | } 55 | 56 | private function changeLocation(event:LocationChangeEvent):void 57 | { 58 | var location:String = webView.location; 59 | 60 | if(location.indexOf("/__/auth/handler?code=") != -1 || location.indexOf("/__/auth/handler?state=") != -1 || location.indexOf("/__/auth/handler#state=") != -1 && location.indexOf("error") == -1){ 61 | 62 | //We are looking for a code parameter in the URL, once we have it we dispose the webview and prepare the last URLRequest 63 | webView.removeEventListener(LocationChangeEvent.LOCATION_CHANGE, changeLocation); 64 | webView.dispose(); 65 | 66 | requestUri = location; 67 | getAccountInfo(); 68 | } 69 | } 70 | 71 | private function getAccountInfo():void 72 | { 73 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 74 | 75 | var myObject:Object = new Object(); 76 | myObject.requestUri = requestUri; 77 | myObject.sessionId = sessionId; 78 | myObject.returnSecureToken = true; 79 | 80 | var request:URLRequest = new URLRequest(FIREBASE_VERIFY_ASSERTION_URL); 81 | request.method = URLRequestMethod.POST; 82 | request.data = JSON.stringify(myObject); 83 | request.requestHeaders.push(header); 84 | 85 | var loader:URLLoader = new URLLoader(); 86 | loader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 87 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 88 | loader.load(request); 89 | } 90 | 91 | private function registerComplete(event:flash.events.Event):void 92 | { 93 | //After a successful login/register we save the response into am easily accessible Object 94 | var rawData:Object = JSON.parse(event.currentTarget.data); 95 | profile = rawData; 96 | 97 | refreshToken(profile.refreshToken); 98 | } 99 | 100 | private function refreshToken(refreshToken:String):void 101 | { 102 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 103 | 104 | var myObject:Object = new Object(); 105 | myObject.grant_type = "refresh_token"; 106 | myObject.refresh_token = refreshToken; 107 | 108 | var request:URLRequest = new URLRequest("https://securetoken.googleapis.com/v1/token?key="+FIREBASE_API_KEY); 109 | request.method = URLRequestMethod.POST; 110 | request.data = JSON.stringify(myObject); 111 | request.requestHeaders.push(header); 112 | 113 | var loader:URLLoader = new URLLoader(); 114 | loader.addEventListener(flash.events.Event.COMPLETE, refreshTokenLoaded); 115 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 116 | loader.load(request); 117 | } 118 | 119 | private function refreshTokenLoaded(event:flash.events.Event):void 120 | { 121 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, refreshTokenLoaded); 122 | 123 | var rawData:Object = JSON.parse(event.currentTarget.data); 124 | authToken = rawData.access_token; 125 | currentState = "GridState"; 126 | } 127 | 128 | protected function selectItem(event:GridSelectionEvent):void 129 | { 130 | titleInput.text = journalGrid.selectedItem.title; 131 | descriptionInput.text = journalGrid.selectedItem.description; 132 | 133 | deleteBtn.enabled = true; 134 | modifyBtn.enabled = true; 135 | } 136 | 137 | /* 138 | Read block 139 | */ 140 | private function loadJournal():void 141 | { 142 | var request:URLRequest = new URLRequest(JOURNAL_URL+profile.localId+".json?auth="+authToken); 143 | 144 | var loader:URLLoader = new URLLoader(); 145 | loader.addEventListener(flash.events.Event.COMPLETE, journalLoaded); 146 | loader.load(request); 147 | } 148 | 149 | private function journalLoaded(event:flash.events.Event):void 150 | { 151 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, journalLoaded); 152 | 153 | //The JSON generated by Firebase contains the id as the node key, we use this function to add it to our Objects 154 | var rawData:Object = JSON.parse(event.currentTarget.data); 155 | var entriesArray:Array = new Array(); 156 | 157 | for (var parent:String in rawData) 158 | { 159 | var tempObject:Object = new Object(); 160 | tempObject.id = parent; 161 | 162 | for (var child:* in rawData[parent]) 163 | { 164 | tempObject[child] = rawData[parent][child]; 165 | } 166 | 167 | entriesArray.push(tempObject); 168 | tempObject = null; 169 | } 170 | 171 | journalGrid.dataProvider = new ArrayList(entriesArray); 172 | 173 | titleInput.text = ""; 174 | descriptionInput.text = ""; 175 | deleteBtn.enabled = false; 176 | modifyBtn.enabled = false; 177 | } 178 | 179 | /* 180 | Insert block 181 | */ 182 | private function saveEntry():void 183 | { 184 | var myObject:Object = new Object(); 185 | myObject.title = titleInput.text; 186 | myObject.description = descriptionInput.text; 187 | myObject.timestamp = new Date().getTime(); 188 | 189 | var request:URLRequest = new URLRequest(JOURNAL_URL+profile.localId+".json?auth="+authToken); 190 | request.data = JSON.stringify(myObject); 191 | request.method = URLRequestMethod.POST; 192 | 193 | var loader:URLLoader = new URLLoader(); 194 | loader.addEventListener(flash.events.Event.COMPLETE, entrySent); 195 | loader.load(request); 196 | } 197 | 198 | private function entrySent(event:flash.events.Event):void 199 | { 200 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, entrySent); 201 | loadJournal(); 202 | } 203 | 204 | /* 205 | Delete block 206 | */ 207 | private function deleteEntry():void 208 | { 209 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 210 | 211 | var request:URLRequest = new URLRequest(JOURNAL_URL+profile.localId+"/"+journalGrid.selectedItem.id+".json?auth="+authToken); 212 | request.method = URLRequestMethod.POST; 213 | request.requestHeaders.push(header); 214 | 215 | var loader:URLLoader = new URLLoader(); 216 | loader.addEventListener(flash.events.Event.COMPLETE, entryDeleted); 217 | loader.load(request); 218 | } 219 | 220 | private function entryDeleted(event:flash.events.Event):void 221 | { 222 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, entryDeleted); 223 | loadJournal(); 224 | } 225 | 226 | /* 227 | Update block 228 | */ 229 | private function updateEntry():void 230 | { 231 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 232 | 233 | var myObject:Object = new Object(); 234 | myObject.title = titleInput.text; 235 | myObject.description = descriptionInput.text; 236 | 237 | var request:URLRequest = new URLRequest(JOURNAL_URL+profile.localId+"/"+journalGrid.selectedItem.id+".json?auth="+authToken); 238 | request.data = JSON.stringify(myObject); 239 | request.method = URLRequestMethod.POST; 240 | request.requestHeaders.push(header); 241 | 242 | var loader:URLLoader = new URLLoader(); 243 | loader.addEventListener(flash.events.Event.COMPLETE, entryUpdated); 244 | loader.load(request); 245 | } 246 | 247 | private function entryUpdated(event:flash.events.Event):void 248 | { 249 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, entryUpdated); 250 | loadJournal(); 251 | } 252 | 253 | /* 254 | Universal Error Handler 255 | */ 256 | private function errorHandler(event:flash.events.IOErrorEvent):void 257 | { 258 | trace(event.currentTarget.data); 259 | } 260 | 261 | ]]> 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /auth/README.md: -------------------------------------------------------------------------------- 1 | # Firebase Auth 2 | 3 | Before using the `Auth` service you need to configure the providers you want to use. 4 | 5 | Each provider requires a different setup process. Individual guides are provided explaining how to achieve this. 6 | 7 | ## Google 8 | 9 | Update: This provider doesn't work anymore with `StageWebView`. The instructions won't be deleted for historical purpose. 10 | 11 | This provider doesn't require special configuration since it is automatically configured when you create your Firebase project. 12 | 13 | Follow these steps to enable it: 14 | 15 | 1. Open the [Firebase console](https://firebase.google.com) and select your project. 16 | 2. Click the `Auth` option in the left side menu. 17 | 3. Click the `SIGN-IN METHOD` button in the top menu and then select `Google` from the providers list. 18 | 4. Click the `Enable` toggle button and set it to `on` and then press the `Save` button. 19 | 20 | The Google provider has been successfully enabled. 21 | 22 | ## Facebook and Twitter 23 | 24 | * Click [here](./facebook) to read the Facebook setup process. 25 | * Click [here](./twitter) to read the Twitter setup process. 26 | 27 | ## Email with Password and Anonymous 28 | *Main guide: [Email & Password Auth](./email)* 29 | 30 | Firebase Auth can also work without using a Federated provider. Email and Anonymous auth have been separated into their own guide. 31 | 32 | ## Implementation (Federated Login) 33 | 34 | Once you have configured one or more Federated providers you will be able to use them in your project. 35 | 36 | Open or create a new project. 37 | 38 | Open the file where you want to implement the Sign-In feature. 39 | 40 | Add the following constants and variables: 41 | 42 | ```actionscript 43 | private static const FIREBASE_API_KEY:String = "YOUR-FIREBASE-APIKEY"; 44 | private static const FIREBASE_CREATE_AUTH_URL:String = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key="+FIREBASE_API_KEY; 45 | private static const FIREBASE_VERIFY_ASSERTION_URL:String = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion?key="+FIREBASE_API_KEY; 46 | private static const FIREBASE_REDIRECT_URL:String = "https://.firebaseapp.com/__/auth/handler"; 47 | private var webView:StageWebView; 48 | private var sessionId:String; 49 | private var requestUri:String; 50 | ``` 51 | 52 | Firebase uses Google Identity Toolkit for its Auth backend. Add one or more buttons and assign them an `EventListener` for when they get clicked/pressed. 53 | 54 | I recommend to use the following function if you are using several providers and call it with the provider of your choice: 55 | 56 | ```actionscript 57 | 58 | private function signInButtonHandler(event:Event):void 59 | { 60 | //The startAuth function only requires one parameter, a String with the domain corresponding to the provider you want to authenticate 61 | startAuth("facebook.com"); //Use this for Facebook 62 | startAuth("google.com"); //Use this for Google 63 | startAuth("twitter.com"); //Use this for Twitter 64 | } 65 | 66 | private function startAuth(provider:String):void 67 | { 68 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 69 | 70 | var myObject:Object = new Object(); 71 | myObject.continueUri = FIREBASE_REDIRECT_URL; 72 | myObject.providerId = provider; 73 | 74 | var request:URLRequest = new URLRequest(FIREBASE_CREATE_AUTH_URL); 75 | request.method = URLRequestMethod.POST; 76 | request.data = JSON.stringify(myObject); 77 | request.requestHeaders.push(header); 78 | 79 | var loader:URLLoader = new URLLoader(); 80 | loader.addEventListener(flash.events.Event.COMPLETE, authURLCreated); 81 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 82 | loader.load(request); 83 | } 84 | 85 | //We also create an errorHandler since Firebase actually gives useful error codes and messages 86 | private function errorHandler(event:flash.events.IOErrorEvent):void 87 | { 88 | trace(event.currentTarget.data); 89 | } 90 | ``` 91 | 92 | This function connects to the Google Identity Toolkit, it requires two parameters: 93 | 94 | * `providerId` which is the domain of the prefered auth provider. 95 | * `continueUri` which is the same URI used for configuring the providers. 96 | 97 | Now we create the `authURLCreated` function where we will read the response: 98 | 99 | ```actionscript 100 | private function authURLCreated(event:flash.events.Event):void 101 | { 102 | var rawData:Object = JSON.parse(event.currentTarget.data); 103 | 104 | //We store the sessionId value from the response for later use 105 | sessionId = rawData.sessionId; 106 | 107 | webView = new StageWebView(); 108 | webView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, changeLocation); 109 | webView.stage = this.stage; 110 | webView.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); 111 | 112 | //We load the URL from the response, it will automatically contain the client id, scopes and the redirect URL 113 | webView.loadURL(rawData.authUri); 114 | } 115 | ``` 116 | 117 | We created and instantiated our `StageWebView` which will load an URL that has been dynamically created by Google Identity Toolkit. 118 | 119 | We also saved the `sessionId` value since it will be used for a later step. 120 | 121 | Now we add the `changeLocation` handler. 122 | 123 | ```actionscript 124 | private function changeLocation(event:LocationChangeEvent):void 125 | { 126 | var location:String = webView.location; 127 | 128 | if(location.indexOf("/__/auth/handler?code=") != -1 || location.indexOf("/__/auth/handler?state=") != -1 || location.indexOf("/__/auth/handler#state=") != -1 && location.indexOf("error") == -1){ 129 | 130 | //We are looking for a code parameter in the URL, once we have it we dispose the webview and prepare the last URLRequest 131 | webView.removeEventListener(LocationChangeEvent.LOCATION_CHANGE, changeLocation); 132 | webView.dispose(); 133 | 134 | requestUri = location; 135 | getAccountInfo(); 136 | } 137 | } 138 | ``` 139 | 140 | Here is where things start getting hard since there's no official documentation. This is what happens: 141 | 142 | 1. Once a successful Sign-In has been made, the StageWebView will change its URL to a 'success' page. 143 | 2. This success page contains a `code` in its URL, thankfully we won't need to parse the code, only check if it exists. 144 | 3. The success page URL varies its form depending on the provider that was used. 145 | 146 | * Facebook success URL code: `?code=` 147 | * Twitter success URL code: `?state=` 148 | * Google success URL code: `#state=` 149 | 150 | In the previous snippet, a conditional was added to detect if the code exists in any of the 3 providers previously mentioned. It also checks that there isn't an error code in the URL. 151 | Once we have an URL that contains the `code` we save it to a String and then call our next function `getAccountInfo()` 152 | 153 | ```actionscript 154 | private function getAccountInfo():void 155 | { 156 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 157 | 158 | var myObject:Object = new Object(); 159 | myObject.requestUri = requestUri; 160 | myObject.sessionId = sessionId; 161 | myObject.returnSecureToken = true; 162 | 163 | var request:URLRequest = new URLRequest(FIREBASE_VERIFY_ASSERTION_URL); 164 | request.method = URLRequestMethod.POST; 165 | request.data = JSON.stringify(myObject); 166 | request.requestHeaders.push(header); 167 | 168 | var loader:URLLoader = new URLLoader(); 169 | loader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 170 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 171 | loader.load(request); 172 | } 173 | ``` 174 | 175 | We created another `URLRequest` with 3 parameters: 176 | 177 | * `requestUri` is the URI that contains the `code`, this code will be parsed by the Google Identity Toolkit service and then used to retrieve the logged in user profile information from the choosen provider. 178 | * `sessionId` is from the very start when we requested the `authUri`. 179 | * `returnSecureToken` is required to obtain a `refreshToken` that will later be exchanged for an `access_token` to authenticate against Firebase Database and Storage. 180 | 181 | Now we add the `registerComplete` function that will contain the logged in user information. 182 | 183 | ```actionscript 184 | private function registerComplete(event:flash.events.Event):void 185 | { 186 | trace(event.currentTarget.data); 187 | var rawData:Object = JSON.parse(event.currentTarget.data); 188 | } 189 | ``` 190 | 191 | If everything was successful you will receive a JSON file with detailed information about the logged in user. You will be able to see the newly registered user on your [Firebase console](https://firebase.gogole.com) in the Auth section. 192 | 193 | This information is formatted the same for all providers, the most important values are: 194 | 195 | Name | Description 196 | ---|--- 197 | `providerId`| A unique id assigned for the provider used in the Sign In process, for example: `facebook.com` or `twitter.com`. 198 | `localId`| A unique id assigned for the logged in user for your specific Firebase project. This is very useful when working with Firebase Database and Firebase Storage. 199 | `refreshToken`| An identity token that is used to identify the current logged in user. The `refreshToken` is used in further Auth requests such as exchanging it for an `access_token`. 200 | `displayName`| The logged in user full name (Google and Facebook) or their handler in Twitter. 201 | `photoUrl`| The logged in user avatar. 202 | `email`| The logged in user email address. 203 | 204 | Note that not all providers return the same information, for example Twitter doesn't return an Email Address. 205 | 206 | Once you have the profile information you might want to save it on an Object that can be globally accessed, you might want to also save it to disk using a `SharedObject` or using the `FileStream` class. 207 | 208 | ## Obtaining and Refreshing an Access Token 209 | 210 | By default the `access_token` has an expiration time of 60 minutes, you can reset its expiration by requesting a fresh one. 211 | To obtain or refresh an `access_token` you only need to provide the following parameters: 212 | 213 | Name | Description 214 | ---|--- 215 | `refreshToken` | A long encoded String that contains user information. You can obtain it from a Sign In request. 216 | `grant_type` | Set to: `refresh_token` 217 | 218 | ```actionscript 219 | private function refreshToken(refreshToken:String):void 220 | { 221 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 222 | 223 | var myObject:Object = new Object(); 224 | myObject.grant_type = "refresh_token"; 225 | myObject.refresh_token = refreshToken; 226 | 227 | var request:URLRequest = new URLRequest("https://securetoken.googleapis.com/v1/token?key="+FIREBASE_API_KEY); 228 | request.method = URLRequestMethod.POST; 229 | request.data = JSON.stringify(myObject); 230 | request.requestHeaders.push(header); 231 | 232 | var loader:URLLoader = new URLLoader(); 233 | loader.addEventListener(flash.events.Event.COMPLETE, refreshTokenLoaded); 234 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 235 | loader.load(request); 236 | } 237 | 238 | private function refreshTokenLoaded(event:flash.events.Event):void 239 | { 240 | var rawData:Object = JSON.parse(event.currentTarget.data); 241 | var accessToken:String = rawData.access_token; 242 | } 243 | 244 | private function errorHandler(event:flash.events.IOErrorEvent):void 245 | { 246 | trace(event.currentTarget.data); 247 | } 248 | ``` 249 | 250 | A successful response will look like the following JSON structure: 251 | 252 | ```json 253 | { 254 | "access_token": "", 255 | "expires_in": "3600", 256 | "token_type": "Bearer", 257 | "refresh_token": "", 258 | "id_token": "", 259 | "user_id": "ZJ7ud0CEpHYPF6QFWRGTe1U1Gvy2", 260 | "project_id": "545203846422" 261 | } 262 | ``` 263 | 264 | Once you have got the `access_token` you are ready to perform secure operations against the Firebase Database and Firebase Storage services. 265 | 266 | In this guide and examples, the `access_token` and `authToken` represent the same value. 267 | -------------------------------------------------------------------------------- /examples/SimpleChat.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | "+tempDate.toLocaleTimeString() + " - " + tempArray[i].senderName+": "; 181 | chatArea.htmlText += tempArray[i].message+"\n"; 182 | chatArea.validateNow(); 183 | chatArea.verticalScrollPosition = chatArea.maxVerticalScrollPosition; 184 | tempDate = null; 185 | } 186 | 187 | //For the individual messages the structure is more simple, we just make sure it contains a message 188 | if(Object(rawData.data).hasOwnProperty("message")) 189 | { 190 | tempDate = new Date(Number(rawData.data["timestamp"])); 191 | chatArea.htmlText += ""+tempDate.toLocaleTimeString() + " - " + rawData.data["senderName"]+": "; 192 | chatArea.htmlText += rawData.data["message"]+"\n"; 193 | chatArea.validateNow(); 194 | chatArea.verticalScrollPosition = chatArea.maxVerticalScrollPosition; 195 | tempDate = null; 196 | } 197 | 198 | //Clean up 199 | tempArray = null; 200 | rawData = null; 201 | message = null; 202 | 203 | } 204 | } 205 | 206 | /* 207 | Send Message Block 208 | */ 209 | private function sendMessage():void 210 | { 211 | if(messageInput.text != "") 212 | { 213 | var myObject:Object = new Object(); 214 | myObject.message = messageInput.text; 215 | myObject.senderId = profile.localId; 216 | myObject.senderName = profile.displayName; 217 | myObject.timestamp = new Date().getTime(); 218 | 219 | var request:URLRequest = new URLRequest(CHATROOM_URL+".json?auth="+authToken); 220 | request.data = JSON.stringify(myObject); 221 | request.method = URLRequestMethod.POST; 222 | 223 | var loader:URLLoader = new URLLoader(); 224 | loader.addEventListener(flash.events.Event.COMPLETE, messageSent); 225 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 226 | loader.load(request); 227 | } 228 | } 229 | 230 | private function messageSent(event:flash.events.Event):void 231 | { 232 | messageInput.text = ""; 233 | } 234 | 235 | /* 236 | Universal Error Handler 237 | */ 238 | private function errorHandler(event:flash.events.IOErrorEvent):void 239 | { 240 | trace(event.currentTarget.data); 241 | 242 | } 243 | 244 | ]]> 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- 1 | # Firebase Database 2 | 3 | Firebase offers a very flexible and secure way to save text-based data. 4 | 5 | This guide will show some of the most common scenarios and it will explain how to use Rules for your database. It is also written from an ActionScript and SQL perspective. 6 | 7 | ## iOS NSAppTransportSecurity 8 | 9 | Before you start integrating Firebase Database in your iOS projects you must add the following exceptions in the `iPhone` section from your descriptor file. 10 | 11 | ```xml 12 | NSAppTransportSecurity 13 | 14 | NSExceptionDomains 15 | 16 | firebaseio.com 17 | 18 | NSIncludesSubdomains 19 | 20 | NSThirdPartyExceptionRequiresForwardSecrecy 21 | 22 | 23 | 24 | 25 | ``` 26 | 27 | Firebase Auth and Storage don't rely on these rules to function. 28 | 29 | ## Understanding the Data 30 | 31 | The data saved in the Firebase database is structured like a tree. Each 'branch' can have its own branches and those sub branches can have their own sub branches. 32 | 33 | ![Example](./images/1.png) 34 | 35 | The Firebase data can only be retrieved as JSON, you will require to use the JSON class to convert the data into an ActionScript Object. 36 | 37 | ## Firebase Rules 38 | 39 | The Firebase Rules are a flexible way to set permissions on who can access certain data. 40 | 41 | By default all the data is private and can only be accessed by Authenticated users. 42 | 43 | To modify the Rules follow these steps: 44 | 45 | 1. Open the [Firebase console](https://firebase.google.com) 46 | 2. Select your project. 47 | 3. Click on the Database option from the left side menu. 48 | 4. Click on `RULES` from the top menu. 49 | 50 | ## Public 51 | 52 | In the following example we will make the contents of a node named `news` available to read for everyone on the Internet. 53 | 54 | ```json 55 | { 56 | "rules": { 57 | "news": { 58 | ".read": true, 59 | ".write": false 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | These rules mean that anyone can read the `news` node, but no one can write (modify) it. 66 | 67 | Now we want to make a public message board where anyone can post anything, one example could be an app that receives anonymous feedback. 68 | 69 | ```json 70 | { 71 | "rules": { 72 | "feedback": { 73 | ".read": true, 74 | ".write": true 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | This is not a very good idea since users that know how Firebase works can manipulate the messages or delete them. 81 | For this case it is recommended to use `Anonymous` auth. 82 | 83 | ## Private (Registered Users only) 84 | 85 | In the following example we will make the contents of a node named `specialoffers` available to read for only registered users from your project. 86 | 87 | ```json 88 | { 89 | "rules": { 90 | "specialoffers": { 91 | ".read": "auth != null", 92 | ".write": false 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | This rule is almost the same as the default one, the only difference is that it specifies which node to protect. 99 | 100 | ## Private (User Specific) 101 | 102 | This is where Firebase auth and rules are best used; each user can have their own data that they can only read and write. 103 | 104 | A common example is an app where users can manage a todo list. 105 | 106 | ```json 107 | { 108 | "rules": { 109 | "todos": { 110 | "$user_id": { 111 | ".read": "$user_id === auth.uid", 112 | ".write": "$user_id === auth.uid" 113 | } 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | We have a main `todos` node. Inside that node each user will have their own sub node. 120 | Each sub node will contain the todos from the specified user. 121 | 122 | The `auth.uid` parameter means the following: 123 | 124 | * `auth` is an Object inside an Authentication Token (see below). 125 | * `uid` is an unique id that is assigned to each user in your Firebase project. This uid is also known as the `localId` 126 | 127 | `$user_id` is an arbitrary variable name that will contain the value from the `auth.uid`, this way the user's node is named the same as its uid. Making impossible to be repeated or be loaded by accident by another user. 128 | 129 | ## Authentication Token 130 | 131 | An Authentication Token is an encoded string that contains information about the user that is trying to perform an operation against the database. 132 | 133 | There are several ways to generate these tokens, this guide will only explain how to do it using Google Identity Toolkit so you won't require to do Cryptographic wizardry. 134 | 135 | For more detailed information on how to generate and manage an `authToken` please consult the [Firebase Auth guide](/../auth). 136 | 137 | Once you have got a fresh `authToken` you are ready to perform secure operations against the Firebase Database and Firebase Storage. 138 | 139 | ## Reading the Database 140 | 141 | Connecting to the database is rather simple, you only require an `URLRequest` and an `URLLoader`. 142 | 143 | To load a Public resource use the following code (this is the equivalent of a `SELECT` in `SQL`): 144 | 145 | ```actionscript 146 | private function loadNews():void 147 | { 148 | var request:URLRequest = new URLRequest("https://.firebaseio.com/news.json"); 149 | 150 | var loader:URLLoader = new URLLoader(); 151 | loader.addEventListener(flash.events.Event.COMPLETE, newsLoaded); 152 | loader.load(request); 153 | } 154 | 155 | private function newsLoaded(event:flash.events.Event):void 156 | { 157 | trace(event.currentTarget.data); 158 | } 159 | ``` 160 | 161 | A simple `GET` request (the default for `URLRequest`) is enough. Remember to always add `.json` after the name of the node you want to read. 162 | 163 | To load a Private resource use the following code: 164 | 165 | ```actionscript 166 | private function loadSpecialOffers(authToken:String):void 167 | { 168 | var request:URLRequest = new URLRequest("https://.firebaseio.com/specialoffers.json?auth="+authToken); 169 | 170 | var loader:URLLoader = new URLLoader(); 171 | loader.addEventListener(flash.events.Event.COMPLETE, offersLoaded); 172 | loader.load(request); 173 | } 174 | 175 | private function offersLoaded(event:flash.events.Event):void 176 | { 177 | trace(event.currentTarget.data); 178 | } 179 | ``` 180 | 181 | Very similar to the previous one, the only difference is the `auth` parameter in the URL. 182 | 183 | ## Realtime Database 184 | 185 | Changes in the database can be read at realtime. Instead of using an `URLLoader` you must use an `URLStream` with a special `URLRequestHeader`. 186 | 187 | In this case we specify to read the contents of the `breakingnews` node. 188 | 189 | ```actionscript 190 | private var myStream:URLStream; 191 | 192 | private function loadLiveFeed():void 193 | { 194 | var header:URLRequestHeader = new URLRequestHeader("Accept", "text/event-stream"); 195 | 196 | var request:URLRequest = new URLRequest("https://.firebaseio.com/breakingnews.json"); 197 | request.requestHeaders.push(header); 198 | 199 | myStream:URLStream = new URLStream(); 200 | myStream.addEventListener(ProgressEvent.PROGRESS, progress); 201 | myStream.load(request); 202 | } 203 | 204 | private function progress(event:ProgressEvent):void 205 | { 206 | var message:String = myStream.readUTFBytes(myStream.bytesAvailable); 207 | trace(message); 208 | } 209 | ``` 210 | 211 | We used a `ProgressEvent.PROGRESS` instead of the usual `Event.COMPLETE`. Everytime the data changes the `ProgressEvent` will be fired with the path and the data that was modified. 212 | 213 | Remember to remove the event listener once you have finished working with the realtime data or it will continue listening to it. 214 | 215 | Auth works exactly the same as with non-realtime data, you only need to provide the `auth` parameter with a valid `authToken` in the URL. 216 | 217 | ## Modifying the Database 218 | 219 | You can add `(INSERT)`, remove `(DELETE)` and modify `(UPDATE)` data from the database. You only need to send your data `JSON` encoded. 220 | 221 | The `auth` parameter is only required when you want to modify private resources. 222 | 223 | ### Adding Data 224 | 225 | In this example we are adding an entry to a node named `journal` with the following parameters: `title`, `description` and `timestamp`. 226 | 227 | ```actionscript 228 | private function saveEntry(title:String, description:String):void 229 | { 230 | var myObject:Object = new Object(); 231 | myObject.title = title; 232 | myObject.description = description; 233 | myObject.timestamp = new Date().getTime(); 234 | 235 | var request:URLRequest = new URLRequest("https://.firebaseio.com/journal.json"); 236 | request.data = JSON.stringify(myObject); 237 | request.method = URLRequestMethod.POST; 238 | 239 | var loader:URLLoader = new URLLoader(); 240 | loader.addEventListener(flash.events.Event.COMPLETE, entrySent); 241 | loader.load(request); 242 | } 243 | 244 | private function entrySent(event:flash.events.Event):void 245 | { 246 | trace(event.currentTarget.data); 247 | } 248 | ``` 249 | 250 | A successful response will look like the following JSON structure: 251 | 252 | ```json 253 | { 254 | "name": "-KRQvdxVELCITxtUynvx" 255 | } 256 | ``` 257 | Everytime you push new data to a node, Firebase will automatically generate an unique `id` for it. The following image shows how data is being structured. 258 | 259 | ![Example](./images/2.png) 260 | 261 | ### Deleting Data 262 | 263 | We can only delete nodes or subnodes but not specific values inside those nodes unless we set those values to blank (see below for modifying data). 264 | 265 | To delete a node you only need to specify its path an add a special `URLRequestHeader`. 266 | 267 | ```actionscript 268 | private function deleteEntry():void 269 | { 270 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 271 | 272 | var request:URLRequest = new URLRequest("https://.firebaseio.com/journal.json"); 273 | request.method = URLRequestMethod.POST; 274 | request.requestHeaders.push(header); 275 | 276 | var loader:URLLoader = new URLLoader(); 277 | loader.addEventListener(flash.events.Event.COMPLETE, entryDeleted); 278 | loader.load(request); 279 | } 280 | 281 | private function entryDeleted(event:flash.events.Event):void 282 | { 283 | trace(event.currentTarget.data); 284 | } 285 | ``` 286 | 287 | This will delete the complete `journal` node including all its subnodes. 288 | 289 | If you only want to delete an specific sub node you must change the url path, in this case we want to delete the node from the previous example: 290 | 291 | `https://.firebaseio.com/journal.json` 292 | 293 | to 294 | 295 | `https://.firebaseio.com/journal/-KRQvdxVELCITxtUynvx.json` 296 | 297 | A successful response returns a `null` value. 298 | 299 | ### Modifying Data 300 | 301 | Sometimes we just want to change a single value from a node. We only need to specify the path of the node, the data to be changed and a special `URLRequestHeader`. 302 | 303 | ```actionscript 304 | private function updateEntry(title:String, description:String):void 305 | { 306 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 307 | 308 | var myObject:Object = new Object(); 309 | myObject.title = title; 310 | myObject.description = description; 311 | 312 | var request:URLRequest = new URLRequest("https://.firebaseio.com/journal/-KRQvdxVELCITxtUynvx.json"); 313 | request.data = JSON.stringify(myObject); 314 | request.method = URLRequestMethod.POST; 315 | request.requestHeaders.push(header); 316 | 317 | var loader:URLLoader = new URLLoader(); 318 | loader.addEventListener(flash.events.Event.COMPLETE, entryUpdated); 319 | loader.load(request); 320 | } 321 | 322 | private function entryUpdated(event:flash.events.Event):void 323 | { 324 | trace(event.currentTarget.data); 325 | } 326 | ``` 327 | 328 | A successful response will contain the values that were modified, in this case the `title` and `description`: 329 | 330 | ```json 331 | { 332 | "title": "New Title", 333 | "description": "Updated description" 334 | } 335 | ``` 336 | 337 | Make sure to always set the correct path or you may modify other nodes by accident. 338 | 339 | ## User Specific Nodes 340 | 341 | At the beginning of this guide we mentioned that Firebase excels at managing user specific data. 342 | 343 | To accomplish this, you only require to add the user `localId` as the node name. 344 | 345 | For example, we want that each user has their independent journal that they can only read and modify, the rules should look as follows: 346 | 347 | ```json 348 | { 349 | "rules": { 350 | "journals": { 351 | "$user_id": { 352 | ".read": "$user_id === auth.uid", 353 | ".write": "$user_id === auth.uid" 354 | } 355 | } 356 | } 357 | } 358 | ``` 359 | 360 | When you want to modify or read their journal you need to specify the users `localId` (known as `uid` inside the rules) and `authToken` as part of the URL. 361 | 362 | ```actionscript 363 | private function loadPrivateJournal(localId:String, authToken:String):void 364 | { 365 | var request:URLRequest = new URLRequest("https://.firebaseio.com/journals/"+localId+".json?auth="+authToken); 366 | 367 | var loader:URLLoader = new URLLoader(); 368 | loader.addEventListener(flash.events.Event.COMPLETE, journalLoaded); 369 | loader.load(request); 370 | } 371 | 372 | private function journalLoaded(event:flash.events.Event):void 373 | { 374 | trace(event.currentTarget.data); 375 | } 376 | ``` 377 | 378 | The `localId` can be obtained after a successful `Sign In`, `Sign Up` or `Get Account Info` request. 379 | 380 | The `auth` value can be obtained after a successful `Refresh Token` request. 381 | 382 | For more information on these values you can read the [Firebase Auth guide](./../auth/). 383 | 384 | ## Firebase Server Variables 385 | 386 | Firebase allows you to use server variables for your fields. At the moment of this writing Firebase only offers a `timestamp` variable. 387 | 388 | To use a server variable you need to create a property on your payload with the following signature: `{".sv": "variable_type"}`. 389 | 390 | In the following example we are using the `timestamp` variable so our message will use a Firebase timestamp instead of a local one from the user's device. 391 | 392 | ```actionscript 393 | private function sendMessage(message:String, username:String):void 394 | { 395 | var myObject:Object = new Object(); 396 | myObject.message = message; 397 | myObject.username = username; 398 | myObject.timestamp = {".sv": "timestamp"}; 399 | 400 | var request:URLRequest = new URLRequest("https://.firebaseio.com/messages.json"); 401 | request.method = URLRequestMethod.POST; 402 | request.data = JSON.stringify(myObject); 403 | 404 | var loader:URLLoader = new URLLoader(); 405 | loader.addEventListener(Event.COMPLETE, messageSent); 406 | loader.load(request); 407 | } 408 | 409 | private function messageSent(event:Event):void 410 | { 411 | trace(event.currentTarget.data); 412 | } 413 | ``` -------------------------------------------------------------------------------- /examples/FileManager.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | .firebaseio.com/images"; 15 | private static const STORAGE_URL:String = "https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/"; 16 | 17 | private var alert:Alert; 18 | private var fileRef:FileReference; 19 | private var profile:Object; 20 | private var authToken:String; 21 | 22 | private function goRegisterState():void 23 | { 24 | this.currentState = "RegisterState"; 25 | } 26 | 27 | private function cancelRegister():void 28 | { 29 | this.currentState = "LoginState"; 30 | } 31 | 32 | protected function selectItem(event:GridSelectionEvent):void 33 | { 34 | downloadBtn.enabled = true; 35 | deleteBtn.enabled = true; 36 | } 37 | 38 | /* 39 | Login Block 40 | */ 41 | protected function login():void 42 | { 43 | if(emailInput.text == "") { 44 | Alert.show("Email is required", "Error"); 45 | } else if(passwordInput.text == "") { 46 | Alert.show("Password is required", "Error"); 47 | } else { 48 | var myObject:Object = new Object(); 49 | myObject.email = emailInput.text; 50 | myObject.password = passwordInput.text; 51 | myObject.returnSecureToken = true; 52 | 53 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 54 | 55 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key="+FIREBASE_API_KEY); 56 | request.method = URLRequestMethod.POST; 57 | request.data = JSON.stringify(myObject); 58 | request.requestHeaders.push(header); 59 | 60 | var loader:URLLoader = new URLLoader(); 61 | loader.addEventListener(flash.events.Event.COMPLETE, loginComplete); 62 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 63 | loader.load(request); 64 | } 65 | } 66 | 67 | private function loginComplete(event:flash.events.Event):void 68 | { 69 | var rawData:Object = JSON.parse(event.currentTarget.data); 70 | profile = rawData; 71 | refreshToken(profile.refreshToken); 72 | } 73 | 74 | /* 75 | Register Block 76 | */ 77 | private function register():void 78 | { 79 | if(registerEmailInput.text == "") { 80 | Alert.show("Email is required", "Error"); 81 | } else if(registerPasswordInput.text == "") { 82 | Alert.show("Password is required", "Error"); 83 | } else { 84 | var myObject:Object = new Object(); 85 | myObject.email = registerEmailInput.text; 86 | myObject.password = registerPasswordInput.text; 87 | myObject.returnSecureToken = true; 88 | 89 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 90 | 91 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key="+FIREBASE_API_KEY); 92 | request.method = URLRequestMethod.POST; 93 | request.data = JSON.stringify(myObject); 94 | request.requestHeaders.push(header); 95 | 96 | var loader:URLLoader = new URLLoader(); 97 | loader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 98 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 99 | loader.load(request); 100 | } 101 | } 102 | 103 | private function registerComplete(event:flash.events.Event):void 104 | { 105 | var rawData:Object = JSON.parse(event.currentTarget.data); 106 | profile = rawData; 107 | refreshToken(profile.refreshToken); 108 | } 109 | 110 | /* 111 | Refresh Token Block 112 | */ 113 | private function refreshToken(refreshToken:String):void 114 | { 115 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 116 | 117 | var myObject:Object = new Object(); 118 | myObject.grant_type = "refresh_token"; 119 | myObject.refresh_token = refreshToken; 120 | 121 | var request:URLRequest = new URLRequest("https://securetoken.googleapis.com/v1/token?key="+FIREBASE_API_KEY); 122 | request.method = URLRequestMethod.POST; 123 | request.data = JSON.stringify(myObject); 124 | request.requestHeaders.push(header); 125 | 126 | var loader:URLLoader = new URLLoader(); 127 | loader.addEventListener(flash.events.Event.COMPLETE, refreshTokenLoaded); 128 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 129 | loader.load(request); 130 | } 131 | 132 | private function refreshTokenLoaded(event:flash.events.Event):void 133 | { 134 | var rawData:Object = JSON.parse(event.currentTarget.data); 135 | authToken = rawData.access_token; 136 | this.currentState = "ManagerState"; 137 | } 138 | 139 | /* 140 | Load Files Block 141 | */ 142 | protected function loadFiles():void 143 | { 144 | var request:URLRequest = new URLRequest(IMAGES_URL+"/"+profile.localId+".json?auth="+authToken); 145 | 146 | var loader:URLLoader = new URLLoader(); 147 | loader.addEventListener(flash.events.Event.COMPLETE, filesLoaded); 148 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 149 | loader.load(request); 150 | } 151 | 152 | private function filesLoaded(event:flash.events.Event):void 153 | { 154 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, filesLoaded); 155 | 156 | //The JSON generated by Firebase contains the id as the node key, we use this function to add it to our Objects 157 | var rawData:Object = JSON.parse(event.currentTarget.data); 158 | var entriesArray:Array = new Array(); 159 | 160 | for (var parent:String in rawData) 161 | { 162 | var tempObject:Object = new Object(); 163 | tempObject.id = parent; 164 | 165 | for (var child:* in rawData[parent]) 166 | { 167 | tempObject[child] = rawData[parent][child]; 168 | } 169 | 170 | entriesArray.push(tempObject); 171 | tempObject = null; 172 | } 173 | 174 | filesGrid.dataProvider = new ArrayList(entriesArray); 175 | downloadBtn.enabled = false; 176 | deleteBtn.enabled = false; 177 | } 178 | 179 | /* 180 | Upload File Block 181 | */ 182 | private function uploadFile():void 183 | { 184 | fileRef = new FileReference(); 185 | fileRef.addEventListener(Event.SELECT, selectHandler); 186 | fileRef.addEventListener(Event.COMPLETE, completeHandler); 187 | 188 | var formatsArray:Array = []; 189 | formatsArray.push(new FileFilter("Images", ".gif;*.jpeg;*.jpg;*.png")); 190 | fileRef.browse(formatsArray); 191 | } 192 | 193 | private function selectHandler(event:Event):void 194 | { 195 | fileRef.load(); 196 | } 197 | 198 | private function completeHandler(event:Event):void 199 | { 200 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 201 | 202 | var request:URLRequest = new URLRequest(STORAGE_URL+"images%2F"+profile.localId+"%2F"+fileRef.name); 203 | request.method = URLRequestMethod.POST; 204 | request.data = fileRef.data; 205 | request.contentType = getMimeType(fileRef.extension); 206 | request.requestHeaders.push(header); 207 | 208 | var loader:URLLoader = new URLLoader(); 209 | loader.addEventListener(flash.events.Event.COMPLETE, uploadComplete); 210 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 211 | loader.load(request); 212 | 213 | alert = Alert.show("Uploading File, Please Wait...", "Uploading"); 214 | } 215 | 216 | private function uploadComplete(event:flash.events.Event):void 217 | { 218 | PopUpManager.removePopUp(alert); 219 | 220 | //The file has been successfully uploaded, we now create a reference of it into the Database 221 | var rawData:Object = JSON.parse(event.currentTarget.data); 222 | var myObject:Object = new Object(); 223 | 224 | for(var key:String in rawData) 225 | { 226 | myObject[key] = rawData[key]; 227 | } 228 | 229 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 230 | var header2:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 231 | 232 | var request:URLRequest = new URLRequest(IMAGES_URL+"/"+profile.localId+"/"+rawData.generation+".json?auth="+authToken); 233 | request.data = JSON.stringify(myObject); 234 | request.method = URLRequestMethod.POST; 235 | request.requestHeaders.push(header); 236 | request.requestHeaders.push(header2); 237 | 238 | var loader:URLLoader = new URLLoader(); 239 | loader.addEventListener(flash.events.Event.COMPLETE, databaseUpdated); 240 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 241 | loader.load(request); 242 | } 243 | 244 | private function databaseUpdated(event:flash.events.Event):void 245 | { 246 | loadFiles(); 247 | } 248 | 249 | /* 250 | Download Image Block 251 | */ 252 | private function downloadImage():void 253 | { 254 | //Hardcoded for this specific example 255 | var tempArray:Array = String(filesGrid.selectedItem.name).split("/"); 256 | var tempName:String = tempArray[2]; 257 | 258 | var request:URLRequest = new URLRequest(STORAGE_URL+formatUrl(filesGrid.selectedItem.name)+"?alt=media&token="+filesGrid.selectedItem.downloadTokens); 259 | 260 | var tempFileRef:FileReference = new FileReference(); 261 | tempFileRef.download(request, tempName); 262 | } 263 | 264 | /* 265 | Delete Blcok 266 | */ 267 | protected function deleteImage():void 268 | { 269 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 270 | var header2:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 271 | 272 | var request:URLRequest = new URLRequest(STORAGE_URL+formatUrl(filesGrid.selectedItem.name)); 273 | trace(request.url); 274 | request.method = URLRequestMethod.POST; 275 | request.requestHeaders.push(header); 276 | request.requestHeaders.push(header2); 277 | 278 | var loader:URLLoader = new URLLoader(); 279 | loader.addEventListener(flash.events.Event.COMPLETE, deleteComplete); 280 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 281 | loader.load(request); 282 | } 283 | 284 | private function deleteComplete(event:flash.events.Event):void 285 | { 286 | //The file has been deleted from Storage, now we delete it from the Database. 287 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 288 | 289 | var request:URLRequest = new URLRequest(IMAGES_URL+"/"+profile.localId+"/"+filesGrid.selectedItem.generation+".json?auth="+authToken); 290 | request.method = URLRequestMethod.POST; 291 | request.requestHeaders.push(header); 292 | 293 | var loader:URLLoader = new URLLoader(); 294 | loader.addEventListener(flash.events.Event.COMPLETE, entryDeleted); 295 | loader.load(request); 296 | } 297 | 298 | private function entryDeleted(event:flash.events.Event):void 299 | { 300 | loadFiles(); 301 | } 302 | 303 | /* 304 | Helper Functions Block 305 | */ 306 | private function formatUrl(url:String):String 307 | { 308 | return url.replace(/\//g, "%2F"); 309 | } 310 | 311 | private function getMimeType(fileType:String):String 312 | { 313 | switch(fileType) 314 | { 315 | case "jpg": 316 | return "image/jpeg"; 317 | break; 318 | case "png": 319 | return "image/png"; 320 | break; 321 | case "gif": 322 | return "image/gif"; 323 | break; 324 | default: 325 | return "image/jpeg"; 326 | } 327 | } 328 | 329 | private function nameFunction(item:Object, column:GridColumn):String 330 | { 331 | //Hardcoded for this specific example 332 | var tempArray:Array = String(item.name).split("/"); 333 | return tempArray[2]; 334 | } 335 | 336 | private function sizeFunction(item:Object, column:GridColumn):String 337 | { 338 | if(int(item.size) >= 1000000){ 339 | return int((item.size/1000000)*100)/100 + " MB"; 340 | } else { 341 | return int((item.size/1000)*100)/100 + " KB"; 342 | } 343 | } 344 | 345 | /* 346 | Universal Error Handler 347 | */ 348 | private function errorHandler(event:flash.events.IOErrorEvent):void 349 | { 350 | trace(event.currentTarget.data); 351 | var rawData:Object = JSON.parse(event.currentTarget.data); 352 | Alert.show(Responses[rawData.error.message], "Error"); 353 | } 354 | 355 | ]]> 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /auth/email/README.md: -------------------------------------------------------------------------------- 1 | # Email and Password 2 | 3 | Firebase provides 2 options when you require a way for your users to log in into your app without using Facebook, Twitter or Google. 4 | 5 | * Email/Password Auth 6 | * Anonymous Auth 7 | 8 | Email and Anonymous Auth also uses the Google Identity Toolkit to achieve this. 9 | 10 | ## Getting Started 11 | 12 | Follow these steps to enable Email/Password Auth: 13 | 14 | 1. Open the [Firebase console](https://firebase.google.com) and select your project. 15 | 2. Click the `Auth` option in the left side menu. 16 | 3. Click the `SIGN-IN METHOD` button in the top menu and then select `Email/Password` from the providers list. 17 | 4. Click the `Enable` toggle button and set it to `on` and then press the `Save` button. 18 | 19 | You might also want to repeat these steps for the `Anonymous` provider only if you want to have Anonymous users. 20 | 21 | ## Implementation 22 | 23 | All the requests must be sent via POST and with the following URLRequestHeader: `"Content-Type", "application/json"`. 24 | 25 | You must also JSON encode the request body. ActionScript offers a built in JSON class to achieve this. 26 | 27 | It is strongly recommended to add an `IOErrorEvent` handler to all the api calls since Firebase returns useful error information. 28 | 29 | ```actionscript 30 | private function errorHandler(event:flash.events.IOErrorEvent):void 31 | { 32 | trace(event.currentTarget.data); 33 | } 34 | ``` 35 | 36 | ## Registering a New User (Sign Up) 37 | 38 | To register a new user you only require to provide the following parameters: 39 | 40 | Name | Description 41 | ---|--- 42 | `email` | A valid formatted Email Address. 43 | `password` | A non weak Password. 44 | `returnSecureToken` | Set to: `true` 45 | 46 | ```actionscript 47 | private function register(email:String, password:String):void 48 | { 49 | var myObject:Object = new Object(); 50 | myObject.email = email; 51 | myObject.password = password; 52 | myObject.returnSecureToken = true; 53 | 54 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 55 | 56 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key="+FIREBASE_API_KEY); 57 | request.method = URLRequestMethod.POST; 58 | request.data = JSON.stringify(myObject); 59 | request.requestHeaders.push(header); 60 | 61 | var loader:URLLoader = new URLLoader(); 62 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 63 | loader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 64 | loader.load(request); 65 | } 66 | 67 | private function registerComplete(event:flash.events.Event):void 68 | { 69 | trace(event.currentTarget.data); 70 | } 71 | ``` 72 | A successful response will look like the following JSON structure: 73 | 74 | ```json 75 | { 76 | "kind": "identitytoolkit#SignupNewUserResponse", 77 | "idToken": "", 78 | "email": "someone@example.com", 79 | "refreshToken": "", 80 | "expiresIn": "3600", 81 | "localId": "I7auXeJz2VgOYWmQajpAyjqYFr23" 82 | } 83 | ``` 84 | The user will be automatically registered in the Auth section from your Firebase console. 85 | 86 | For an Anonymous approach you don't need to specify anything in the request body. You will still get a response similar to the above just without an Email Address. 87 | 88 | The `idToken` received from this response is used to perform further account management requests. 89 | The `refreshToken` is used to get an `access_token` for Auth requests. For more information see the bottom of this page. 90 | 91 | ## Verifying Credentials (Sign In) 92 | 93 | To sign in an user you only require to provide the following parameters: 94 | 95 | Name | Description 96 | ---|--- 97 | `email` | The user's Email Address. 98 | `password` | The user's Password. 99 | `returnSecureToken` | Set to: `true` 100 | 101 | ```actionscript 102 | private function login(email:String, password:String):void 103 | { 104 | var myObject:Object = new Object(); 105 | myObject.email = email; 106 | myObject.password = password; 107 | myObject.returnSecureToken = true; 108 | 109 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 110 | 111 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key="+FIREBASE_API_KEY); 112 | request.method = URLRequestMethod.POST; 113 | request.data = JSON.stringify(myObject); 114 | request.requestHeaders.push(header); 115 | 116 | var loader:URLLoader = new URLLoader(); 117 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 118 | loader.addEventListener(flash.events.Event.COMPLETE, loginComplete); 119 | loader.load(request); 120 | } 121 | 122 | private function loginComplete(event:flash.events.Event):void 123 | { 124 | trace(event.currentTarget.data); 125 | } 126 | ``` 127 | 128 | A successful response will look like the following JSON structure: 129 | 130 | ```json 131 | { 132 | "kind": "identitytoolkit#VerifyPasswordResponse", 133 | "localId": "I7auXeJz2VgOYWmQajpAyjqYFr23", 134 | "email": "someone@example.com", 135 | "displayName": "", 136 | "idToken": "", 137 | "registered": true, 138 | "refreshToken": "", 139 | "expiresIn": "3600" 140 | } 141 | ``` 142 | 143 | Note that failing to enter the correct password 3 times in a row will block the IP for future login attempts for a while. 144 | 145 | The `idToken` received from this response is used to perform further account management requests. 146 | The `refreshToken` is used to get an `access_token` for Auth requests. For more information see the bottom of this page. 147 | 148 | ## Password Reset 149 | 150 | To reset a password you only require to provide the following parameters: 151 | 152 | Name | Description 153 | ---|--- 154 | `email` | The Email Address you want to send the Password recovery email. 155 | `requestType` | Set to: `PASSWORD_RESET` 156 | 157 | ```actionscript 158 | private function resetPassword(emai:String):void 159 | { 160 | var myObject:Object = new Object(); 161 | myObject.email = email; 162 | myObject.requestType = "PASSWORD_RESET"; 163 | 164 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 165 | 166 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key="+FIREBASE_API_KEY); 167 | request.method = URLRequestMethod.POST; 168 | request.data = JSON.stringify(myObject); 169 | request.requestHeaders.push(header); 170 | 171 | var loader:URLLoader = new URLLoader(); 172 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 173 | loader.addEventListener(flash.events.Event.COMPLETE, resetPasswordComplete); 174 | loader.load(request); 175 | } 176 | 177 | private function resetPasswordComplete(event:flash.events.Event):void 178 | { 179 | trace(event.currentTarget.data); 180 | } 181 | ``` 182 | 183 | A successful response will look like the following JSON structure: 184 | 185 | ```json 186 | { 187 | "kind": "identitytoolkit#GetOobConfirmationCodeResponse", 188 | "email": "someone@example.com" 189 | } 190 | ``` 191 | 192 | An email with instructions will be sent to the desired email address. You can customize the template of emails in the Auth section from the Firebase console. 193 | 194 | ## Verify Email 195 | 196 | When you require that Email Addresses are actually real you can prompt the user to confirm their Email Address by sending them an email with a confirmation link. 197 | 198 | This is commonly used in message boards and ecommerce solutions. 199 | 200 | This method is similar to the Reset Password one, you need to provide the following parameters: 201 | 202 | Name | Description 203 | ---|--- 204 | `email` | The Email Address you want to verify. 205 | `requestType` | Set to: `VERIFY_EMAIL` 206 | `idToken` | A long encoded String that contains user information. You can obtain this String from the response in the Sign Up and Sign In methods. 207 | 208 | ```actionscript 209 | private function verifyEmail(idToken:String, email:String):void 210 | { 211 | var myObject:Object = new Object(); 212 | myObject.email = email; 213 | myObject.idToken = idToken; 214 | myObject.requestType = "VERIFY_EMAIL"; 215 | 216 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 217 | 218 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key="+FIREBASE_API_KEY); 219 | request.method = URLRequestMethod.POST; 220 | request.data = JSON.stringify(myObject); 221 | request.requestHeaders.push(header); 222 | 223 | var loader:URLLoader = new URLLoader(); 224 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 225 | loader.addEventListener(flash.events.Event.COMPLETE, verifyEmailComplete); 226 | loader.load(request); 227 | } 228 | 229 | private function verifyEmailComplete(event:flash.events.Event):void 230 | { 231 | trace(event.currentTarget.data); 232 | } 233 | ``` 234 | 235 | A successful response will look like the following JSON structure: 236 | 237 | ```json 238 | { 239 | "kind": "identitytoolkit#GetOobConfirmationCodeResponse", 240 | "email": "someone@example.com" 241 | } 242 | ``` 243 | 244 | An email with instructions will be sent to the desired email address. You can customize the template of emails in the Auth section from the Firebase console. 245 | 246 | ## Get Account Info 247 | 248 | This method is used for retrieving the logged in user information, very useful to check if a user has confirmed their Email Address. 249 | 250 | This method only requires a valid Email Address and an `idToken`. You should call this method right after a Sign In or Sign Up request since those methods return a fresh `idToken`. 251 | 252 | ```actionscript 253 | private function getAccountInfo(idToken:String, email:String):void 254 | { 255 | var myObject:Object = new Object(); 256 | myObject.email = email; 257 | myObject.idToken = idToken; 258 | 259 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 260 | 261 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key="+FIREBASE_API_KEY); 262 | request.method = URLRequestMethod.POST; 263 | request.data = JSON.stringify(myObject); 264 | request.requestHeaders.push(header); 265 | 266 | var loader:URLLoader = new URLLoader(); 267 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 268 | loader.addEventListener(flash.events.Event.COMPLETE, getAccountInfoComplete); 269 | loader.load(request); 270 | } 271 | 272 | private function getAccountInfoComplete(event:flash.events.Event):void 273 | { 274 | trace(event.currentTarget.data); 275 | } 276 | ``` 277 | 278 | A successful response will look like the following JSON structure: 279 | 280 | ```json 281 | { 282 | "kind": "identitytoolkit#GetAccountInfoResponse", 283 | "users": [ 284 | { 285 | "localId": "I7auXeJz2VgOYWmQajpAyjqYFr23", 286 | "email": "someone@example.com", 287 | "emailVerified": true, 288 | "providerUserInfo": [ 289 | { 290 | "providerId": "password", 291 | "federatedId": "someone@example.com", 292 | "email": "someone@example.com", 293 | "rawId": "someone@example.com" 294 | } 295 | ], 296 | "passwordHash": "UkVEQUNURUQ=", 297 | "passwordUpdatedAt": 1.473621716E12, 298 | "validSince": "1473621716", 299 | "lastLoginAt": "1473625365000", 300 | "createdAt": "1473621716000" 301 | } 302 | ] 303 | } 304 | ``` 305 | ## Set Account Info 306 | 307 | To change the Email and or Password for an account you only require to specify which fields do you want to change and provide a valid `idToken` 308 | 309 | ```actionscript 310 | private function setAccountInfo(idToken:String, email:String = null, password:String = null):void 311 | { 312 | var myObject:Object = new Object(); 313 | //You can comment the email or password values if you don't need to change them 314 | myObject.email = email; 315 | myObject.password = password; 316 | myObject.idToken = idToken; 317 | 318 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 319 | 320 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key="+FIREBASE_API_KEY); 321 | request.method = URLRequestMethod.POST; 322 | request.data = JSON.stringify(myObject); 323 | request.requestHeaders.push(header); 324 | 325 | var loader:URLLoader = new URLLoader(); 326 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 327 | loader.addEventListener(flash.events.Event.COMPLETE, setAccountInfoComplete); 328 | loader.load(request); 329 | } 330 | 331 | private function setAccountInfoComplete(event:flash.events.Event):void 332 | { 333 | trace(event.currentTarget.data); 334 | } 335 | ``` 336 | 337 | A successful response from a Password change will look like the following JSON structure: 338 | 339 | ```json 340 | { 341 | "kind": "identitytoolkit#SetAccountInfoResponse", 342 | "localId": "I7auXeJz2VgOYWmQajpAyjqYFr23", 343 | "email": "someone@example.com", 344 | "passwordHash": "UkXEHANURUR=", 345 | "providerUserInfo": [ 346 | { 347 | "providerId": "password", 348 | "federatedId": "someone@example.com" 349 | } 350 | ] 351 | } 352 | ``` 353 | 354 | A successful response from an Email change will look like the following JSON structure: 355 | 356 | ```json 357 | { 358 | "kind": "identitytoolkit#SetAccountInfoResponse", 359 | "localId": "I7auXeJz2VgOYWmQajpAyjqYFr23", 360 | "email": "someone@example2.com", 361 | "passwordHash": "UkXEHANURUR=", 362 | "providerUserInfo": [ 363 | { 364 | "providerId": "password", 365 | "federatedId": "someone@example2.com" 366 | } 367 | ], 368 | "idToken": "" 369 | } 370 | ``` 371 | 372 | The Email Address is updated to the new one but it needs to be confirmed or it will turn back to its previous state. An email containing a confirmation link is automatically sent to the original Email Address. 373 | 374 | ## Delete Account 375 | 376 | To delete an account you only require to provide a valid `idToken`. 377 | 378 | ```actionscript 379 | private function deleteAccount(idToken:String):void 380 | { 381 | var myObject:Object = new Object(); 382 | myObject.idToken = idToken; 383 | 384 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 385 | 386 | var request:URLRequest = new URLRequest("https://www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount?key="+FIREBASE_API_KEY); 387 | request.method = URLRequestMethod.POST; 388 | request.data = JSON.stringify(myObject); 389 | request.requestHeaders.push(header); 390 | 391 | var loader:URLLoader = new URLLoader(); 392 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 393 | loader.addEventListener(flash.events.Event.COMPLETE, accountDeleted); 394 | loader.load(request); 395 | } 396 | 397 | private function accountDeleted(event:flash.events.Event):void 398 | { 399 | trace(event.currentTarget.data); 400 | } 401 | ``` 402 | 403 | A successful response will look like the following JSON structure: 404 | 405 | ```json 406 | { 407 | "kind": "identitytoolkit#DeleteAccountResponse" 408 | } 409 | ``` 410 | 411 | ## Obtaining and Refreshing an Access Token 412 | 413 | By default the `access_token` has an expiration time of 60 minutes, you can reset its expiration by requesting a fresh one. 414 | To obtain or refresh an `access_token` you only need to provide the following parameters: 415 | 416 | Name | Description 417 | ---|--- 418 | `refreshToken` | A long encoded String that contains user information. You can obtain it from a Sign In request. 419 | `grant_type` | Set to: `refresh_token` 420 | 421 | ```actionscript 422 | private function refreshToken(refreshToken:String):void 423 | { 424 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 425 | 426 | var myObject:Object = new Object(); 427 | myObject.grant_type = "refresh_token"; 428 | myObject.refresh_token = refreshToken; 429 | 430 | var request:URLRequest = new URLRequest("https://securetoken.googleapis.com/v1/token?key="+FIREBASE_API_KEY); 431 | request.method = URLRequestMethod.POST; 432 | request.data = JSON.stringify(myObject); 433 | request.requestHeaders.push(header); 434 | 435 | var loader:URLLoader = new URLLoader(); 436 | loader.addEventListener(flash.events.Event.COMPLETE, refreshTokenLoaded); 437 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 438 | loader.load(request); 439 | } 440 | 441 | private function refreshTokenLoaded(event:flash.events.Event):void 442 | { 443 | var rawData:Object = JSON.parse(event.currentTarget.data); 444 | var accessToken:String = rawData.access_token; 445 | } 446 | 447 | private function errorHandler(event:flash.events.IOErrorEvent):void 448 | { 449 | trace(event.currentTarget.data); 450 | } 451 | ``` 452 | 453 | A successful response will look like the following JSON structure: 454 | 455 | ```json 456 | { 457 | "access_token": "", 458 | "expires_in": "3600", 459 | "token_type": "Bearer", 460 | "refresh_token": "", 461 | "id_token": "", 462 | "user_id": "ZJ7ud0CEpHYPF6QFWRGTe1U1Gvy2", 463 | "project_id": "545203846422" 464 | } 465 | ``` 466 | 467 | Once you have got the `access_token` you are ready to perform secure operations against the Firebase Database and Firebase Storage services. 468 | 469 | In this guide and examples, the `access_token` and `authToken` represent the same value. -------------------------------------------------------------------------------- /storage/README.md: -------------------------------------------------------------------------------- 1 | # Firebase Storage 2 | 3 | Firebase Storage is based on Google Cloud Storage, a very easy and flexible solution for storing all kinds of files. 4 | 5 | Files are stored the same way as in your personal computer, using a tree hierarchy. This means there's a root folder which can contain more folders and those folders can contain additional folders and files. 6 | 7 | It is strongly recommended to avoid the use of special characters when naming files and folders. 8 | 9 | You will need special care for the slash character `(/)`. I recommend using this helper function to URL encode them: 10 | 11 | ```actionscript 12 | private function formatUrl(url:String):String 13 | { 14 | return url.replace(/\//g, "%2F"); 15 | } 16 | ``` 17 | 18 | In the context of this guide a `bucket` is a synonymous to your Firebase project. 19 | 20 | ## Firebase Rules 21 | 22 | The Firebase Rules are a flexible way to set permissions on who can access certain files and data. 23 | 24 | By default all the data is private and can only be accessed by Authenticated users. 25 | 26 | To modify the Rules follow these steps: 27 | 28 | 1. Open the [Firebase console](https://firebase.google.com) 29 | 2. Select your project. 30 | 3. Click on the Storage option from the left side menu. 31 | 4. Click on `RULES` from the top menu. 32 | 33 | ## Default Rules 34 | 35 | ``` 36 | service firebase.storage { 37 | match /b/.appspot.com/o { 38 | match /{allPaths=**} { 39 | allow read, write: if request.auth != null; 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | These rules are very similar to the `Auth` default rules. They mean that any authenticated user can upload, delete and modify all files from your bucket. 46 | 47 | ## Public Reading and Writing 48 | 49 | The following rules allows any user to upload, delete and modify files from your entire bucket. Use this only while developing and testing. 50 | 51 | ``` 52 | service firebase.storage { 53 | match /b/.appspot.com/o { 54 | match /{allPaths=**} { 55 | allow read, write; 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | ## Public Reading 62 | 63 | Use the following rules if you need to host some files that anyone on the Internet can download, such as images, documents, audio and video. 64 | 65 | ``` 66 | service firebase.storage { 67 | match /b/.appspot.com/o { 68 | match /{allPaths=**} { 69 | allow read; 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | The following rules will allow anyone to read but not to write the contents of a folder named `public`. 76 | 77 | ``` 78 | service firebase.storage { 79 | match /b/.appspot.com/o { 80 | match /public/} { 81 | allow read; 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ## Prerequisites 88 | 89 | 90 | Since Firebase returns useful error information we will use the following `Event.COMPLETE` and `IOErrorEvent.IOERROR` listeners in all of our requests. 91 | 92 | ```actionscript 93 | private function taskComplete(event:flash.events.Event):void 94 | { 95 | trace(event.currentTarget.data); 96 | } 97 | 98 | private function errorHandler(event:flash.events.Event):void 99 | { 100 | trace(event.currentTarget.data); 101 | } 102 | ``` 103 | 104 | ## Uploading a File 105 | 106 | To upload a file with `URLLoader` you require to send it as a `ByteArray`. 107 | 108 | If you upload the same file to the same location, it will be replaced with new metadata. 109 | 110 | In this example we are uploading a file from a predefined location. A common example is syncing a save game after a game session. 111 | 112 | ```actionscript 113 | private function uploadFile():void 114 | { 115 | var file:File = File.applicationStorageDirectory.resolvePath("savegame.data"); 116 | 117 | var fileStream:FileStream = new FileStream(); 118 | fileStream.open(file, FileMode.READ); 119 | 120 | var bytes:ByteArray = new ByteArray(); 121 | fileStream.readBytes(bytes); 122 | fileStream.close(); 123 | 124 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 125 | request.method = URLRequestMethod.POST; 126 | request.data = bytes; 127 | request.contentType = "text/plain"; 128 | 129 | var loader:URLLoader = new URLLoader(); 130 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 131 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 132 | loader.load(request); 133 | } 134 | ``` 135 | 136 | A successful response will look like the following JSON structure: 137 | 138 | ```json 139 | { 140 | "name": "savegames/savegame.data", 141 | "bucket": ".appspot.com", 142 | "generation": "1473948546121000", 143 | "metageneration": "1", 144 | "contentType": "text/plain", 145 | "timeCreated": "2016-09-15T14:09:06.053Z", 146 | "updated": "2016-09-15T14:09:06.053Z", 147 | "storageClass": "STANDARD", 148 | "size": "10450", 149 | "md5Hash": "7aIjAPS+Sd0DaF5SmGTUYw==", 150 | "contentEncoding": "identity", 151 | "crc32c": "DObTDw==", 152 | "etag": "CKj6iJzGkc8CEAE=", 153 | "downloadTokens": "7232aa46-f2e1-4df5-9698-d9c77b88ad5f" 154 | } 155 | ``` 156 | 157 | Your new file and a `savegames` folder will instantly appear in the Storage section from the Firebase console. 158 | 159 | The `contentType` doesn't need to be accurate, but it is recommended to set it properly. 160 | 161 | ## Uploading with Progress Indicator 162 | 163 | You can also upload files using the `upload` and `uploadUnencoded` methods from the `File` and `FileReference` classes. 164 | 165 | This example will demonstrate how to upload a file from a fixed location and retrieve the upload progress. 166 | 167 | ```actionscript 168 | private function uploadFile():void 169 | { 170 | var file:File = File.applicationStorageDirectory.resolvePath("heavy_picture.jpg"); 171 | file.addEventListener(ProgressEvent.PROGRESS, progressHandler); 172 | file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, uploadCompleteDataHandler); 173 | 174 | var fileStream:FileStream = new FileStream(); 175 | fileStream.open(file, FileMode.READ); 176 | 177 | var bytes:ByteArray = new ByteArray(); 178 | fileStream.readBytes(bytes); 179 | fileStream.close(); 180 | 181 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/pictures%2F"+"heavy_picture.jpg"); 182 | request.method = URLRequestMethod.POST; 183 | request.data = bytes.toString(); 184 | request.contentType = "image/jpeg"; 185 | 186 | file.uploadUnencoded(request); 187 | } 188 | 189 | private function progressHandler(event:ProgressEvent):void 190 | { 191 | var progress:Number = Math.round((event.bytesLoaded/event.bytesTotal)*100); 192 | trace("Upload Progress: " + progress + "%"); 193 | } 194 | 195 | private function uploadCompleteDataHandler(event:DataEvent):void 196 | { 197 | trace(event.data); //Here you will receive the file metadata from Firebase Storage. 198 | } 199 | ``` 200 | 201 | It is required to send the file as a `String` that represents the file bytes and use the `uploadUnencoded` method. 202 | 203 | ## Uploading a File with Auth 204 | 205 | Authorizing requests for Firebase Storage is a bit different than in Firebase Database. Instead of adding an `auth` parameter in the URL with the `authToken`, we add it into a header. 206 | 207 | ```actionscript 208 | private function uploadFile(authToken:String):void 209 | { 210 | var file:File = File.applicationStorageDirectory.resolvePath("savegame.data"); 211 | 212 | var fileStream:FileStream = new FileStream(); 213 | fileStream.open(file, FileMode.READ); 214 | 215 | var bytes:ByteArray = new ByteArray(); 216 | fileStream.readBytes(bytes); 217 | fileStream.close(); 218 | 219 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 220 | 221 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 222 | request.method = URLRequestMethod.POST; 223 | request.data = bytes; 224 | request.contentType = "text/plain"; 225 | request.requestHeaders.push(header); 226 | 227 | var loader:URLLoader = new URLLoader(); 228 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 229 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 230 | loader.load(request); 231 | } 232 | ``` 233 | 234 | A successful response will look like the following JSON structure: 235 | 236 | ```json 237 | { 238 | "name": "savegames/savegame.data", 239 | "bucket": ".appspot.com", 240 | "generation": "1473948546121000", 241 | "metageneration": "1", 242 | "contentType": "text/plain", 243 | "timeCreated": "2016-09-15T14:09:06.053Z", 244 | "updated": "2016-09-15T14:09:06.053Z", 245 | "storageClass": "STANDARD", 246 | "size": "10450", 247 | "md5Hash": "7aIjAPS+Sd0DaF5SmGTUYw==", 248 | "contentEncoding": "identity", 249 | "crc32c": "DObTDw==", 250 | "etag": "CKj6iJzGkc8CEAE=", 251 | "downloadTokens": "7232aa46-f2e1-4df5-9698-d9c77b88ad5f" 252 | } 253 | ``` 254 | 255 | ## Deleting a File 256 | 257 | Deleting a file is very simple, you only need to send a `DELETE` request with the file you want to delete. 258 | 259 | Instead of using a `DELETE` request we are going to use an alternative but valid approach, the `"X-HTTP-Method-Override", "DELETE"` header. 260 | 261 | The reason to use the header is to have consistency with the Firebase Database guide. 262 | 263 | ```actionscript 264 | private function deleteFile():void 265 | { 266 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 267 | 268 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 269 | request.method = URLRequestMethod.POST; 270 | request.requestHeaders.push(header); 271 | 272 | var loader:URLLoader = new URLLoader(); 273 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 274 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 275 | loader.load(request); 276 | } 277 | ``` 278 | 279 | A successful response will return an [empty String](https://cloud.google.com/storage/docs/json_api/v1/objects/delete). 280 | 281 | ## Deleting a File with Auth 282 | 283 | To delete a file with authentication you only need to provide an `authToken` in the `Authorization` header and the file path in a `DELETE` request. 284 | 285 | ```actionscript 286 | private function deleteFile(authToken:String):void 287 | { 288 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 289 | var header2:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 290 | 291 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 292 | request.method = URLRequestMethod.POST; 293 | request.requestHeaders.push(header); 294 | request.requestHeaders.push(header2); 295 | 296 | var loader:URLLoader = new URLLoader(); 297 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 298 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 299 | loader.load(request); 300 | } 301 | ``` 302 | 303 | ## Updating Metadata 304 | 305 | To modify the metadata generated after your upload a file you will only require to `JSON` encode which fields do you need to update and send them in a `PATCH` request. This is very similar as updating the Firebase Database data. 306 | 307 | Click [here](https://firebase.google.com/docs/storage/web/file-metadata) for a list of all the fields that can be modified. In the following example we are going to change the `contentType`. 308 | 309 | ```actionscript 310 | private function updateMetadata():void 311 | { 312 | var myObject:Object = new Object(); 313 | myObject.contentType = "application/binary"; 314 | 315 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 316 | var header2:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 317 | 318 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/"+"savegames%2F"+"savegame.data"); 319 | request.method = URLRequestMethod.POST; 320 | request.data = JSON.stringify(myObject); 321 | request.requestHeaders.push(header); 322 | request.requestHeaders.push(header2); 323 | 324 | var loader:URLLoader = new URLLoader(); 325 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 326 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 327 | loader.load(request); 328 | } 329 | ``` 330 | 331 | A successful response will look like the following JSON structure: 332 | 333 | ```json 334 | { 335 | "name": "savegames/savegame.data", 336 | "bucket": ".appspot.com", 337 | "generation": "1473948546121000", 338 | "metageneration": "2", 339 | "contentType": "application/binary", 340 | "timeCreated": "2016-09-15T14:09:06.053Z", 341 | "updated": "2016-09-16T02:46:44.439Z", 342 | "storageClass": "STANDARD", 343 | "size": "10450", 344 | "md5Hash": "7aIjAPS+Sd0DaF5SmGTUYw==", 345 | "contentEncoding": "identity", 346 | "crc32c": "DObTDw==", 347 | "etag": "CKj6iJzGkc8CEAE=", 348 | "downloadTokens": "7232aa46-f2e1-4df5-9698-d9c77b88ad5f" 349 | } 350 | ``` 351 | 352 | ## Updating Metadata with Auth 353 | 354 | To update metadata with authentication you need to provide an `authToken` in the `Authorization` header. 355 | 356 | You will also require to `JSON` encode which fields do you need to update and send them in a `PATCH` request. 357 | 358 | ```actionscript 359 | private function updateMetadata(authToken:String):void 360 | { 361 | var myObject:Object = new Object(); 362 | myObject.contentType = "application/binary"; 363 | 364 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 365 | var header2:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 366 | var header3:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 367 | 368 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 369 | request.method = URLRequestMethod.POST; 370 | request.data = JSON.stringify(myObject); 371 | request.requestHeaders.push(header); 372 | request.requestHeaders.push(header2); 373 | request.requestHeaders.push(header3); 374 | 375 | var loader:URLLoader = new URLLoader(); 376 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 377 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 378 | loader.load(request); 379 | } 380 | ``` 381 | 382 | ## Downloading a File 383 | 384 | To download files from your Firebase Storage bucket you only require to send a `GET` request with the full path of the file and the parameter `alt=media`. 385 | You will also require the followinv values from the `JSON` structure. 386 | 387 | Name | Description 388 | ---|--- 389 | `name` | The path of the file including its name. 390 | `bucket` | Your Firebase Project ID plus the `appspot.com` domain. 391 | `downloadTokens` | A String used for downloading private files. 392 | 393 | ```json 394 | { 395 | "name": "savegames/savegame.data", 396 | "bucket": ".appspot.com", 397 | "generation": "1473948546121000", 398 | "metageneration": "1", 399 | "contentType": "text/plain", 400 | "timeCreated": "2016-09-15T14:09:06.053Z", 401 | "updated": "2016-09-15T14:09:06.053Z", 402 | "storageClass": "STANDARD", 403 | "size": "10450", 404 | "md5Hash": "7aIjAPS+Sd0DaF5SmGTUYw==", 405 | "contentEncoding": "identity", 406 | "crc32c": "DObTDw==", 407 | "etag": "CKj6iJzGkc8CEAE=", 408 | "downloadTokens": "7232aa46-f2e1-4df5-9698-d9c77b88ad5f" 409 | } 410 | ``` 411 | 412 | There are several ways to download files using the AIR runtime, we are going to use the easiest one: `navigateToURL()`. 413 | 414 | The following example downloads a `public` file: 415 | 416 | ```actionscript 417 | private function downloadFile():void 418 | { 419 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"+"?alt=media"); 420 | navigateToURL(request); 421 | } 422 | ``` 423 | 424 | ## Downloading a Private File 425 | 426 | Downloading `private` files doesn't require the `Authorization` header. You only require to provide the `token` parameter and the file path. 427 | 428 | The `token` parameter is the `downloadTokens` value from the `JSON` response when you upload a file. 429 | 430 | ```actionscript 431 | private function downloadFile(downloadTokens:String):void 432 | { 433 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"+"?alt=media&token="+downloadTokens); 434 | navigateToURL(request); 435 | } 436 | ``` 437 | 438 | ## Downloading Metadata 439 | 440 | You can download the information of any file in JSON format without downloading the file itself. 441 | 442 | To download the metadata of a `public` file you only require to send a `GET` request with the full file path. 443 | 444 | ```actionscript 445 | private function downloadMetadata():void 446 | { 447 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 448 | 449 | var loader:URLLoader = new URLLoader(); 450 | loader.addEventListener(flash.events.event.COMPLETE, metadataLoaded); 451 | loader.load(request); 452 | } 453 | 454 | private function metadataLoaded(event:flash.events.Event):void 455 | { 456 | trace(event.currentTarget.data); 457 | } 458 | ``` 459 | 460 | ## Downloading Private Metadata 461 | 462 | To download metadata from `private` files you require to provide an `authToken` in the `Authorization` header. 463 | 464 | ```actionscript 465 | private function downloadMetadata(authToken:String):void 466 | { 467 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 468 | 469 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+"savegame.data"); 470 | request.method = URLRequestMethod.POST; 471 | request.requestHeaders.push(header); 472 | 473 | var loader:URLLoader = new URLLoader(); 474 | loader.addEventListener(flash.events.event.COMPLETE, metadataLoaded); 475 | loader.load(request); 476 | } 477 | 478 | private function metadataLoaded(event:flash.events.Event):void 479 | { 480 | trace(event.currentTarget.data); 481 | } 482 | ``` 483 | 484 | ## User Specific Files 485 | 486 | So far we have worked with the same file (`savegame.data`) in the same location (`savegames` folder), 487 | now we are going to step it up and make it so every registered user can have their own folder with their respective `savegame.data` file. 488 | 489 | The following rules specify that only authenticated users can read and write the file `savegame.data` that will be located inside a folder named the same as their `uid` (`localId`): 490 | 491 | ``` 492 | service firebase.storage { 493 | match /b/.appspot.com/o { 494 | match /savegames/{userId}/savegame.data { 495 | allow read, write: if request.auth.uid == userId; 496 | } 497 | } 498 | } 499 | ``` 500 | 501 | We can use the following rules if we want users to have control over their complete folder: 502 | 503 | ``` 504 | service firebase.storage { 505 | match /b/.appspot.com/o { 506 | match /savegames/{userId}/{allPaths=**} { 507 | allow read, write: if request.auth.uid == userId; 508 | } 509 | } 510 | } 511 | ``` 512 | 513 | The following snippet requires that you already have a valid `authToken` and a `localId`. 514 | 515 | The `localId` can be obtained after a successful `Sign In`, `Sign Up` or `Get Account Info` request. 516 | 517 | The `auth` value can be obtained after a successful `Refresh Token` request. 518 | 519 | For more information on these values you can read the [Firebase Auth guide](./../auth/). 520 | 521 | ```actionscript 522 | private function uploadPersonalFile(authToken:String, localId:String):void 523 | { 524 | var file:File = File.applicationStorageDirectory.resolvePath("savegame.data"); 525 | 526 | var fileStream:FileStream = new FileStream(); 527 | fileStream.open(file, FileMode.READ); 528 | var bytes:ByteArray = new ByteArray(); 529 | fileStream.readBytes(bytes); 530 | fileStream.close(); 531 | 532 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer "+authToken); 533 | 534 | var request:URLRequest = new URLRequest("https://firebasestorage.googleapis.com/v0/b/.appspot.com/o/savegames%2F"+localId+"%2F"+"savegame.data"); 535 | request.method = URLRequestMethod.POST; 536 | request.data = bytes; 537 | request.contentType = "text/plain"; 538 | request.requestHeaders.push(header); 539 | 540 | var loader:URLLoader = new URLLoader(); 541 | loader.addEventListener(flash.events.Event.COMPLETE, taskComplete); 542 | loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, errorHandler); 543 | loader.load(request); 544 | } 545 | ``` 546 | 547 | A successful response will look like the following JSON structure: 548 | 549 | ```json 550 | { 551 | "name": "savegames/ktfSpKHar2fW1fcZePigI0Zr0bP2/savegame.data", 552 | "bucket": ".appspot.com", 553 | "generation": "1473948546121000", 554 | "metageneration": "1", 555 | "contentType": "text/plain", 556 | "timeCreated": "2016-09-15T14:09:06.053Z", 557 | "updated": "2016-09-16T02:46:44.439Z", 558 | "storageClass": "STANDARD", 559 | "size": "10450", 560 | "md5Hash": "7aIjAPS+Sd0DaF5SmGTUYw==", 561 | "contentEncoding": "identity", 562 | "crc32c": "DObTDw==", 563 | "etag": "CKj6iJzGkc8CEAE=", 564 | "downloadTokens": "7232aa46-f2e1-4df5-9698-d9c77b88ad5f" 565 | } 566 | ``` 567 | 568 | You will notice that the `localId` value has been added to the name. --------------------------------------------------------------------------------