├── 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 | 
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 | 
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 | 
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 |
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 | [](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 | 
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 | 
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.
--------------------------------------------------------------------------------