├── .github ├── FUNDING.yml └── workflows │ ├── bob.yml │ └── trigger-site-rebuild.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── add_facebook_analytics.png ├── add_new_app_menu.png ├── add_new_app_platform.png ├── add_platform.png ├── app_dashboard.png ├── facebook_analytics_open_dashboard.png ├── facebook_analytics_settings.png ├── facebook_analytics_show_events.png ├── game_project.png ├── html5_bundle.png ├── index.md ├── new_app_id.png ├── register_dev.png ├── register_verify.png ├── select_platform.png ├── settings_android.png ├── settings_canvas.png ├── settings_ios.png └── simple_hosting.png ├── facebook ├── api │ └── facebook.script_api ├── ext.manifest ├── lib │ └── web │ │ └── library_facebook.js ├── manifests │ ├── android │ │ ├── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── com-facebook-android.pro │ │ └── extension.pro │ ├── ios │ │ ├── Info.plist │ │ └── Podfile │ └── web │ │ └── engine_template.html ├── misc │ ├── README_DEBUG_LOCAL.md │ ├── create_cert.sh │ └── facebook_server.py ├── src │ ├── facebook.cpp │ ├── facebook_analytics.cpp │ ├── facebook_analytics.h │ ├── facebook_android.cpp │ ├── facebook_emscripten.cpp │ ├── facebook_ios.mm │ ├── facebook_private.cpp │ ├── facebook_private.h │ ├── facebook_util.cpp │ ├── facebook_util.h │ └── java │ │ └── com │ │ └── defold │ │ └── facebook │ │ ├── Facebook.java │ │ ├── FacebookActivity.java │ │ └── FacebookJNI.java └── test │ ├── compile_cl.bat │ ├── compile_clang.sh │ ├── jc_test.h │ ├── run_tests.bat │ ├── run_tests.sh │ ├── test_fb.cpp │ └── wscript ├── game.project ├── generated.appmanifest ├── input └── game.input_binding └── main ├── facebook.gui ├── facebook.gui_script ├── gui.atlas ├── images ├── background.png ├── logo.png ├── splash.png ├── white-disc.png ├── white-logo.png └── white-ring.png └── main.collection /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: defold 2 | patreon: Defold 3 | custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NBNBHTUW4GS4C'] 4 | -------------------------------------------------------------------------------- /.github/workflows/bob.yml: -------------------------------------------------------------------------------- 1 | name: Build with bob 2 | 3 | on: 4 | push: 5 | schedule: 6 | # nightly at 05:00 on the 1st and 15th 7 | - cron: 0 5 1,15 * * 8 | 9 | env: 10 | VERSION_FILENAME: 'info.json' 11 | BUILD_SERVER: 'https://build.defold.com' 12 | 13 | jobs: 14 | build_with_bob: 15 | strategy: 16 | matrix: 17 | platform: [armv7-android, x86_64-linux, x86_64-win32, x86-win32, js-web] 18 | runs-on: ubuntu-latest 19 | 20 | name: Build 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-java@v3 24 | with: 25 | java-version: '17.0.5+8' 26 | distribution: 'temurin' 27 | 28 | - name: Get Defold version 29 | run: | 30 | TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'` 31 | echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV 32 | echo "Found version ${TMPVAR}" 33 | 34 | - name: Download bob.jar 35 | run: | 36 | wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar 37 | java -jar bob.jar --version 38 | 39 | - name: Resolve libraries 40 | run: java -jar bob.jar resolve --email a@b.com --auth 123456 41 | - name: Build 42 | run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}} 43 | - name: Bundle 44 | run: java -jar bob.jar --platform=${{ matrix.platform }} bundle 45 | 46 | # macOS is not technically needed for building, but we want to test bundling as well, since we're also testing the manifest merging 47 | build_with_bob_macos: 48 | strategy: 49 | matrix: 50 | platform: [arm64-ios, x86_64-darwin] 51 | runs-on: macOS-latest 52 | 53 | name: Build 54 | steps: 55 | - uses: actions/checkout@v2 56 | - uses: actions/setup-java@v3 57 | with: 58 | java-version: '17.0.5+8' 59 | distribution: 'temurin' 60 | 61 | - name: Get Defold version 62 | run: | 63 | TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'` 64 | echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV 65 | echo "Found version ${TMPVAR}" 66 | 67 | - name: Download bob.jar 68 | run: | 69 | wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar 70 | java -jar bob.jar --version 71 | 72 | - name: Resolve libraries 73 | run: java -jar bob.jar resolve --email a@b.com --auth 123456 74 | - name: Build 75 | run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}} 76 | - name: Bundle 77 | run: java -jar bob.jar --platform=${{ matrix.platform }} bundle 78 | -------------------------------------------------------------------------------- /.github/workflows/trigger-site-rebuild.yml: -------------------------------------------------------------------------------- 1 | name: Trigger site rebuild 2 | 3 | on: [push] 4 | 5 | jobs: 6 | site-rebuild: 7 | runs-on: ubuntu-latest 8 | 9 | steps: [ 10 | { 11 | name: 'Repository dispatch', 12 | uses: defold/repository-dispatch@1.2.1, 13 | with: { 14 | repo: 'defold/defold.github.io', 15 | token: '${{ secrets.SERVICES_GITHUB_TOKEN }}', 16 | user: 'services@defold.se', 17 | action: 'extension-facebook' 18 | } 19 | }] 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .internal 2 | build 3 | .DS_Store 4 | Thumbs.db 5 | *.pem 6 | .gradle 7 | facebook/libhelper/android/build/** 8 | *.der 9 | /.editor_settings -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Defold Foundation 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Facebook for Defold 2 | 3 | Defold [native extension](https://www.defold.com/manuals/extensions/) which provides access to Facebook functionality on Android, iOS and HTML5. 4 | 5 | [Manual, API and setup instructions](https://www.defold.com/extension-facebook/) is available on the official Defold site. 6 | -------------------------------------------------------------------------------- /docs/add_facebook_analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/add_facebook_analytics.png -------------------------------------------------------------------------------- /docs/add_new_app_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/add_new_app_menu.png -------------------------------------------------------------------------------- /docs/add_new_app_platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/add_new_app_platform.png -------------------------------------------------------------------------------- /docs/add_platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/add_platform.png -------------------------------------------------------------------------------- /docs/app_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/app_dashboard.png -------------------------------------------------------------------------------- /docs/facebook_analytics_open_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/facebook_analytics_open_dashboard.png -------------------------------------------------------------------------------- /docs/facebook_analytics_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/facebook_analytics_settings.png -------------------------------------------------------------------------------- /docs/facebook_analytics_show_events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/facebook_analytics_show_events.png -------------------------------------------------------------------------------- /docs/game_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/game_project.png -------------------------------------------------------------------------------- /docs/html5_bundle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/html5_bundle.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Defold Facebook documentation 3 | brief: This manual covers how to setup and use Facebook in Defold. 4 | --- 5 | 6 | # Defold Facebook documentation 7 | 8 | This extension allows you to interact with Facebook's game connectivity features in a uniform way for games on iOS, Android and HTML5. The Defold Facebook extension brings the various platform specific Facebook APIs under a unified set of functions that work the same on iOS, Android and HTML5 (through Facebook Canvas). To get started with Facebook connectivity in your games, you need a Facebook account. 9 | 10 | 11 | ## Installation 12 | To use this library in your Defold project, add the following URL to your `game.project` dependencies: 13 | 14 | [https://github.com/defold/extension-facebook/archive/master.zip](https://github.com/defold/extension-facebook/archive/master.zip) 15 | 16 | We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-facebook/releases). 17 | 18 | 19 | ## Registering as a Facebook developer 20 | 21 | To develop for Facebook you must sign up as a Facebook developer. This allows you to create Facebook applications that your Defold game can communicate with. 22 | 23 | * Head over to [Facebook for developers](https://developers.facebook.com) 24 | * Log in with your Facebook account. 25 | * Follow the instructions to register and verify your developer account. 26 | 27 | ![Register as a developer](register_dev.png) 28 | ![ developer](register_verify.png) 29 | 30 | 31 | ## Creating a Facebook app 32 | 33 | The next step is to create a Facebook application. The My Apps menu in the corner lists your apps, and there is an option to Add a New App. 34 | 35 | ![Add new app](add_new_app_menu.png) 36 | 37 | You are presented with a selection of target platforms. Click *basic setup* to skip the wizards. 38 | 39 | ::: sidenote 40 | Most information provided through the wizards is irrelevant when developing on Defold. In particular, you usually don't need to edit *Info.plist* or *AndroidManifest.xml* yourself. Defold does that work for you. 41 | ::: 42 | 43 | ![Add new app platform](add_new_app_platform.png) 44 | 45 | You can easily add, remove and change platform settings in the app dashboard. You are now asked to name your app and select *Display Name*, *Namespace* and *Category*. Again, these can all be edited in the app dashboard. When you have gone through this, Facebook creates an app with a unique app identifier for you. The *App ID* is not possible to change since it identifies this specific app. 46 | 47 | ![New app id](new_app_id.png) 48 | 49 | ![App dashboard settings](add_platform.png) 50 | 51 | Click the *Settings* tab and note the numerical *App ID*. Next, in the Advanced section of the *Settings* tab on the Facebook app page, scroll to the *Security* section and note the *Client token*. 52 | 53 | Now, back in the *Settings* tab on the Facebook app page, click *+ Add Platform* to add a new platform to the app. Each platform has a set of settings to fill in. 54 | 55 | ![Select platform](select_platform.png) 56 | 57 | 58 | ## Configuring your Defold app 59 | 60 | The *App ID* and *Client token* need to go into the [project settings](/manuals/project-settings) of your Defold game. Start by opening the *game.project* file **using an external text editor** and add the following lines to the end of the file (remember to save the changes): 61 | 62 | ``` 63 | [facebook] 64 | appid = 0 65 | autoinit = 0 66 | clienttoken = 0 67 | ``` 68 | 69 | Next, go back to Defold and Open the *game.project* file from the *Assets pane* and scroll to the *Facebook* section and add the *App ID* to the `Appid` field and the *Client token* to the `Clienttoken` field. 70 | 71 | ![game.project settings](game_project.png) 72 | 73 | ## iOS 74 | 75 | For iOS you need to specify the game's `bundle_identifier` as specified in *game.project*. 76 | 77 | ![iOS settings](settings_ios.png) 78 | 79 | 80 | ## Android 81 | 82 | For Android you need to specify a *Google Play Package Name*, which is the game's *package* identifier specified in *game.project*. You should also generate hashes of the certificate(s) you use and enter them into the *Key Hashes* field. [Follow the instructions](https://developers.facebook.com/docs/android/getting-started/#release-key-hash) in the Facebook Developer pages to generate the hashes. 83 | 84 | ![Android settings](settings_android.png) 85 | 86 | 87 | ## Facebook Canvas 88 | 89 | For HTML5 games, the process is a bit different. Facebook needs access to your game content from somewhere. There are two options: 90 | 91 | ![Facebook Canvas settings](settings_canvas.png) 92 | 93 | 1. Use Facebook's *Simple Application Hosting*. Click *Yes* to select managed hosting. Select *uploaded assets* to open the hosted asset manager. 94 | 95 | ![Simple hosting](simple_hosting.png) 96 | 97 | Select that you want to host a "HTML5 Bundle": 98 | 99 | ![HTML5 bundle](html5_bundle.png) 100 | 101 | Compress your HTML5 bundle into a .7z or .zip archive and upload it to Facebook. Make sure to click *Push to production* to start serving the game. 102 | 103 | 2. The alternative to Facebook hosting is to upload a HTML5 bundle of your game to some server of your choice that serves the game through HTTPS. Set the *Secure Canvas URL* to the URL of your game. 104 | 105 | The game now works through the Facebook URL provided as *Canvas Page*. 106 | 107 | ## Limited Login (iOS) 108 | 109 | Starting with Facebook SDK 17.0 (Defold Facebook Extension 8.0.0+), new APIs has been added for the use of Limited Login. 110 | 111 | * `facebook.login_with_tracking_preference()` - Login to Facebook and request a set of permissions. Allows developers to signal that a login is limited in terms of tracking users. 112 | * `facebook.get_current_authentication_token()` - This function returns the currently stored authentication token after a previous successful login. 113 | * `facebook.get_current_profile()` - After your application receives the logged-in user’s authentication token, you can use this function to read information that user has granted to your application. 114 | * `facebook.LOGIN_TRACKING_ENABLED` - available only if in App Tracking Transparency request user enabled tracking. 115 | * `facebook.LOGIN_TRACKING_LIMITED` - should beused if in App Tracking Transparency request user disabled tracking. 116 | 117 | If the user has disabled tracking but you still attempt to log in using `facebook.login_with_permissions()` or its equivalent `facebook.login_with_tracking_preference()` with `facebook.LOGIN_TRACKING_ENABLED`, you will receive a Facebook Access token, but it will not be valid. 118 | 119 | More information about the changes is available in the official Facebook documentation: 120 | * [Changes made to Facebook Login SDK for iOS](https://developers.facebook.com/blog/post/2024/03/28/changes-made-to-fb-login-sdk/) 121 | * [Limited Login for iOS](https://developers.facebook.com/docs/facebook-login/limited-login/ios) 122 | 123 | ## Testing the setup 124 | 125 | The following basic test can be used to see if things are set up properly. 126 | 127 | 1. Create a new game object and attach a script component with a new script file to it. 128 | 2. Enter the following code in the script file (this requires the use of Facebook extension v.2 or later): 129 | 130 | ```lua 131 | local function get_me_callback(self, id, response) 132 | -- The response table includes all the response data 133 | pprint(response) 134 | end 135 | 136 | local function fb_login(self, data) 137 | if data.status == facebook.STATE_OPEN then 138 | -- Logged in ok. Let's try reading some "me" data through the 139 | -- HTTP graph API. 140 | local token = facebook.access_token() 141 | local url = "https://graph.facebook.com/me/?access_token=" .. token 142 | http.request(url, "GET", get_me_callback) 143 | elseif data.status == facebook.STATE_CLOSED_LOGIN_FAILED then 144 | -- Do something to indicate that login failed 145 | end 146 | if data.error then 147 | -- An error occurred 148 | else 149 | -- No error 150 | end 151 | end 152 | 153 | function init(self) 154 | -- Log in with read permissions. 155 | local permissions = { "public_profile", "email" } 156 | facebook.login_with_permissions(permissions, facebook.AUDIENCE_EVERYONE, fb_login) 157 | end 158 | ``` 159 | 160 | Running this simple test should display something like the following in the console: 161 | 162 | ```txt 163 | DEBUG:SCRIPT: 164 | { 165 | status = 200, 166 | headers = { 167 | connection = keep-alive, 168 | date = Fri, 04 Nov 2016 13:54:33 GMT, 169 | etag = "0725a4f703fe6af27da183cfec0bb22637e331e0", 170 | access-control-allow-origin = *, 171 | content-length = 53, 172 | expires = Sat, 01 Jan 2000 00:00:00 GMT, 173 | content-type = text/javascript; charset=UTF-8, 174 | x-fb-debug = Pr1qUssb8Xa3x3r1t913hHMdefh69DSYYV5vcxeOB7O33mcfShIw+r7BoLpn147I2wzLF2CZRTpnR3/VYOtFpA==, 175 | facebook-api-version = v2.5, 176 | cache-control = private, no-cache, no-store, must-revalidate, 177 | pragma = no-cache, 178 | x-fb-trace-id = F03S5dtsdaS, 179 | x-fb-rev = 2664414, 180 | } 181 | response = {"name":"Max de Fold ","id":"14159265358979323"}, 182 | } 183 | ``` 184 | 185 | * The full Defold Facebook API is documented on the [extension GitHub page](https://defold.github.io/extension-facebook/). 186 | * The Facebook Graph API is documented here: https://developers.facebook.com/docs/graph-api 187 | 188 | 189 | ## Deferred Facebook SDK initialization 190 | 191 | The Facebook SDK must be initialized before any of the functions of the SDK can be used. The default behaviour is for the Facebook SDK to be automatically initialized on startup. If manual Facebook SDK initialization is preferred then set the `facebook.autoinit` property to 0 in *game.project* and call `facebook.init()` to initialize the SDK. 192 | 193 | 194 | ## Facebook Analytics 195 | 196 | Facebook Analytics allows you as a developer to get aggregated demographics and rich insights, like how many people launch your app, how often people make purchases and many other interactions. 197 | 198 | ### Setup 199 | 200 | Before you can use Facebook Analytics you need to create a Facebook app and add the *App ID* to *game.project* as described above. Next step is to add the Analytics product to your Facebook app: 201 | 202 | ![Add Facebook Analytics](add_facebook_analytics.png) 203 | 204 | Once you have added the Facebook Analytics product to your Facebook app you can optionally configure the Analytics settings. Refer to the Facebook Developer documentation for more information about the different settings: 205 | 206 | ![Add Facebook Analytics](facebook_analytics_settings.png) 207 | 208 | ### Usage 209 | 210 | When you have Facebook Analytics added to your Facebook app you can immediately start using it from your application by posting analytics events: 211 | 212 | ```lua 213 | function init(self) 214 | -- post a spent credits event 215 | local params = { [facebook.PARAM_LEVEL] = 30, [facebook.PARAM_NUM_ITEMS] = 2 } 216 | facebook.post_event(facebook.EVENT_SPENT_CREDITS, 25, params) 217 | 218 | -- post a custom event 219 | local level = 19 220 | local params = { kills = 23, gold = 4, xp = 890 } 221 | facebook.post_event("level_completed", level, params) 222 | end 223 | ``` 224 | 225 | Available predefined events and parameters can be seen in the [Facebook extension API reference](https://defold.com/extension-facebook/facebook_api/). These should correlate to the [Standard Events](https://developers.facebook.com/docs/analytics/send_data/events#standard) and [Parameters](https://developers.facebook.com/docs/analytics/send_data/events#parameter) from the Facebook developer documentation. 226 | 227 | The Facebook SDK used by Defold will also automatically generate several events, such as app install and app launch. Refer to the [Auto-logged events](https://developers.facebook.com/docs/analytics/send_data/events#autologged) in the Facebook developer documentation. 228 | 229 | Once you have sent some events they will show up in the Facebook Analytics dashboard, accessible from your Facebook app page via the *View Analytics* button: 230 | 231 | ![Add Facebook Analytics](facebook_analytics_open_dashboard.png) 232 | 233 | Events are accessed from the *Events* option on the from the Facebook Analytics page: 234 | 235 | ![Add Facebook Analytics](facebook_analytics_show_events.png) 236 | 237 | 238 | ### Sharing events with Facebook 239 | 240 | You can opt to share generated events with Facebook for improved ad-tracking. This is done via the [`enable_event_usage()`](https://defold.github.io/extension-facebook/#enable_event_usage) and [`disable_event_usage()`](https://defold.github.io/extension-facebook/#disable_event_usage) functions. The default setting is according to the Facebook SDK documentation to not share events. 241 | 242 | 243 | ## Relevant Facebook documentation links 244 | 245 | * [Facebook Login](https://developers.facebook.com/docs/facebook-login) 246 | 247 | * [Permissions](https://developers.facebook.com/docs/facebook-login/permissions) 248 | 249 | * [Login Best Practices](https://developers.facebook.com/docs/facebook-login/best-practices) 250 | 251 | 252 | ## Source code 253 | 254 | The source code is available on [GitHub](https://github.com/defold/extension-facebook) 255 | 256 | -------------------------------------------------------------------------------- /docs/new_app_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/new_app_id.png -------------------------------------------------------------------------------- /docs/register_dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/register_dev.png -------------------------------------------------------------------------------- /docs/register_verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/register_verify.png -------------------------------------------------------------------------------- /docs/select_platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/select_platform.png -------------------------------------------------------------------------------- /docs/settings_android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/settings_android.png -------------------------------------------------------------------------------- /docs/settings_canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/settings_canvas.png -------------------------------------------------------------------------------- /docs/settings_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/settings_ios.png -------------------------------------------------------------------------------- /docs/simple_hosting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/docs/simple_hosting.png -------------------------------------------------------------------------------- /facebook/ext.manifest: -------------------------------------------------------------------------------- 1 | name: FacebookExtExternal 2 | 3 | platforms: 4 | android: 5 | context: 6 | aaptExtraPackages: ['com.facebook', 'com.facebook.common', 'com.facebook.login', 'com.facebook.share'] 7 | 8 | arm64-ios: 9 | context: 10 | flags: ['-fcxx-modules', '-fmodules', '-fobjc-weak', '-Wno-module-import-in-extern-c'] 11 | linkFlags: ['-ObjC', '-Wl,-rpath,/usr/lib/swift', '-Wl,-rpath,@executable_path/Frameworks'] 12 | libs: ['swiftCompatibility51', 'swiftCompatibility50'] 13 | 14 | x86_64-ios: 15 | context: 16 | flags: ['-fcxx-modules', '-fmodules', '-fobjc-weak', '-Wno-module-import-in-extern-c'] 17 | linkFlags: ['-ObjC', '-Wl,-rpath,/usr/lib/swift', '-Wl,-rpath,@executable_path/Frameworks'] 18 | libs: ['swiftCompatibility51', 'swiftCompatibility50'] 19 | 20 | 21 | -------------------------------------------------------------------------------- /facebook/lib/web/library_facebook.js: -------------------------------------------------------------------------------- 1 | var LibraryFacebook = { 2 | $FBinner: { 3 | loginTimestamp: -1, 4 | needsReauth: false 5 | }, 6 | 7 | dmFacebookInitialize: function(app_id, version) { 8 | // We assume that the Facebook javascript SDK is loaded by now. 9 | // This should be done via a script tag (synchronously) in the html page: 10 | // 11 | // This script tag MUST be located before the engine (game) js script tag. 12 | try { 13 | FB.init({ 14 | appId : UTF8ToString(app_id), 15 | status : false, 16 | xfbml : false, 17 | version : UTF8ToString(version), 18 | }); 19 | 20 | window._dmFacebookUpdatePermissions = function(callback) { 21 | try { 22 | FB.api('/me/permissions', function (response) { 23 | var e = (response && response.error ? response.error.message : 0); 24 | if(e == 0 && response.data) { 25 | var permissions = []; 26 | for (var i=0; i 0) { 62 | var currentTimeStamp = (!Date.now ? +new Date() : Date.now()); 63 | var delta = (currentTimeStamp - FBinner.loginTimestamp) / 1000; 64 | if (delta >= response.reauthorize_required_in) { 65 | FBinner.needsReauth = true; 66 | {{{ makeDynCall('vii', 'callback') }}}(lua_state, 0); 67 | return; 68 | } 69 | } 70 | 71 | var buf = stringToUTF8OnStack(access_token); 72 | {{{ makeDynCall('vii', 'callback') }}}(lua_state, buf); 73 | } else { 74 | {{{ makeDynCall('vii', 'callback') }}}(lua_state, 0); 75 | } 76 | } catch (e){ 77 | console.error("Facebook access token failed " + e); 78 | } 79 | }, 80 | 81 | // https://developers.facebook.com/docs/javascript/reference/FB.ui 82 | dmFacebookShowDialog: function(params, mth, callback, lua_state) { 83 | var par = JSON.parse(UTF8ToString(params)); 84 | par.method = UTF8ToString(mth); 85 | 86 | try { 87 | FB.ui(par, function(response) { 88 | // https://developers.facebook.com/docs/graph-api/using-graph-api/v2.0 89 | // (Section 'Handling Errors') 90 | var e = (response && response.error ? response.error.message : 0); 91 | if(e == 0) { 92 | var res_data = JSON.stringify(response); 93 | var res_buf = stringToUTF8OnStack(res_data); 94 | {{{ makeDynCall('viii', 'callback') }}}(lua_state, res_buf, e); 95 | } else { 96 | var error = stringToUTF8OnStack(e); 97 | {{{ makeDynCall('viii', 'callback') }}}(lua_state, 0, error); 98 | } 99 | }); 100 | } catch (e) { 101 | console.error("Facebook show dialog failed " + e); 102 | } 103 | }, 104 | 105 | // https://developers.facebook.com/docs/reference/javascript/FB.AppEvents.LogEvent 106 | dmFacebookPostEvent: function(event, value_to_sum, keys, values) { 107 | var params = {}; 108 | try { 109 | event = UTF8ToString(event); 110 | keys = JSON.parse(UTF8ToString(keys)); 111 | values = JSON.parse(UTF8ToString(values)); 112 | for (var i = 0; i < keys.length; ++i) { 113 | params[keys[i]] = values[i]; 114 | } 115 | } catch (e) { 116 | console.error("Unable to parse data from Defold: " + e); 117 | } 118 | 119 | try { 120 | FB.AppEvents.logEvent(event, value_to_sum, params); 121 | } catch (e) { 122 | console.error("Unable to post event to Facebook Analytics: " + e); 123 | } 124 | }, 125 | 126 | // https://developers.facebook.com/docs/javascript/reference/v2.0 127 | dmFacebookEnableEventUsage: function() { 128 | console.error("Limiting Facebook Analytics is not supported for Canvas"); 129 | }, 130 | 131 | // https://developers.facebook.com/docs/javascript/reference/v2.0 132 | dmFacebookDisableEventUsage: function() { 133 | console.error("Limiting Facebook Analytics is not supported for Canvas"); 134 | }, 135 | 136 | dmFacebookDoLogout: function() { 137 | try { 138 | FB.logout(function(response) { 139 | // user is now logged out 140 | }); 141 | } catch (e){ 142 | console.error("Facebook logout failed " + e); 143 | } 144 | }, 145 | 146 | dmFacebookLoginWithPermissions: function( 147 | state_open, state_closed, state_failed, permissions, callback, thread) { 148 | try { 149 | var opts = {scope: UTF8ToString(permissions)}; 150 | if (FBinner.needsReauth) { 151 | opts.auth_type = "reauthorize"; 152 | FBinner.needsReauth = false; 153 | } 154 | 155 | FB.login(function(response) { 156 | var error = response && response.error ? response.error.message : 0; 157 | if (error == 0 && response.authResponse) { 158 | 159 | FBinner.loginTimestamp = (!Date.now ? +new Date() : Date.now()); 160 | 161 | window._dmFacebookUpdatePermissions(function (_error, _permissions) { 162 | if (_error == 0) { 163 | var permissionsbuf = stringToUTF8OnStack(_permissions); 164 | {{{ makeDynCall('viiii', 'callback') }}}(thread, state_open, 0, permissionsbuf); 165 | } else { 166 | var errbuf = stringToUTF8OnStack(_error); 167 | {{{ makeDynCall('viiii', 'callback') }}}(thread, state_failed, errbuf, 0); 168 | } 169 | }); 170 | } else if (error != 0) { 171 | var errbuf = stringToUTF8OnStack(error); 172 | {{{ makeDynCall('viiii', 'callback') }}}(thread, state_closed, errbuf, 0); 173 | } else { 174 | var errmsg = "Login was cancelled"; 175 | var errbuf = stringToUTF8OnStack(errmsg); 176 | {{{ makeDynCall('viiii', 'callback') }}}(thread, state_failed, errbuf, 0); 177 | } 178 | }, opts); 179 | } catch (error) { 180 | console.error("An unexpected error occurred during Facebook JavaScript interaction: " + error); 181 | } 182 | } 183 | } 184 | 185 | autoAddDeps(LibraryFacebook, '$FBinner'); 186 | addToLibrary(LibraryFacebook); 187 | -------------------------------------------------------------------------------- /facebook/manifests/android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /facebook/manifests/android/build.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | } 4 | 5 | dependencies { 6 | // https://developers.facebook.com/docs/android/getting-started/ 7 | implementation 'com.facebook.android:facebook-core:17.0.0' 8 | implementation 'com.facebook.android:facebook-login:17.0.0' 9 | implementation 'com.facebook.android:facebook-share:17.0.0' 10 | implementation 'com.facebook.android:facebook-messenger:17.0.0' 11 | implementation 'com.facebook.android:facebook-applinks:17.0.0' 12 | } 13 | -------------------------------------------------------------------------------- /facebook/manifests/android/com-facebook-android.pro: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-present, Facebook, Inc. All rights reserved. 2 | # 3 | # You are hereby granted a non-exclusive, worldwide, royalty-free license to use, 4 | # copy, modify, and distribute this software in source code or binary form for use 5 | # in connection with the web services and APIs provided by Facebook. 6 | # 7 | # As with any software that integrates with the Facebook platform, your use of 8 | # this software is subject to the Facebook Developer Principles and Policies 9 | # [http://developers.facebook.com/policy/]. This copyright notice shall be 10 | # included in all copies or substantial portions of the software. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 14 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 16 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 17 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | 19 | # To enable ProGuard in your project, edit project.properties 20 | # to define the proguard.config property as described in that file. 21 | # 22 | # Add project specific ProGuard rules here. 23 | # By default, the flags in this file are appended to flags specified 24 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 25 | # You can edit the include path and order by changing the ProGuard 26 | # include property in project.properties. 27 | # 28 | # For more details, see 29 | # http://developer.android.com/guide/developing/tools/proguard.html 30 | 31 | # Add any project specific keep options here: 32 | 33 | # If your project uses WebView with JS, uncomment the following 34 | # and specify the fully qualified class name to the JavaScript interface 35 | # class: 36 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 37 | # public *; 38 | #} 39 | 40 | -keepclassmembers class * implements java.io.Serializable { 41 | private static final java.io.ObjectStreamField[] serialPersistentFields; 42 | private void writeObject(java.io.ObjectOutputStream); 43 | private void readObject(java.io.ObjectInputStream); 44 | java.lang.Object writeReplace(); 45 | java.lang.Object readResolve(); 46 | } 47 | 48 | -keepnames class com.facebook.FacebookActivity 49 | -keepnames class com.facebook.CustomTabActivity 50 | 51 | -keep class com.facebook.places.Places 52 | -------------------------------------------------------------------------------- /facebook/manifests/android/extension.pro: -------------------------------------------------------------------------------- 1 | # The actual extension code 2 | -keep class com.defold.facebook.** { 3 | *; 4 | } 5 | -------------------------------------------------------------------------------- /facebook/manifests/ios/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSExceptionDomains 8 | 9 | facebook.com 10 | 11 | NSIncludesSubdomains 12 | 13 | NSThirdPartyExceptionRequiresForwardSecrecy 14 | 15 | 16 | fbcdn.net 17 | 18 | NSIncludesSubdomains 19 | 20 | NSThirdPartyExceptionRequiresForwardSecrecy 21 | 22 | 23 | akamaihd.net 24 | 25 | NSIncludesSubdomains 26 | 27 | NSThirdPartyExceptionRequiresForwardSecrecy 28 | 29 | 30 | 31 | 32 | LSApplicationQueriesSchemes 33 | 34 | fbapi 35 | fbapi20130214 36 | fbapi20130410 37 | fbapi20130702 38 | fbapi20131010 39 | fbapi20131219 40 | fbapi20140410 41 | fbapi20140116 42 | fbapi20150313 43 | fbapi20150629 44 | fbauth 45 | fbauth2 46 | fb-messenger-api20140430 47 | fb-messenger-platform-20150128 48 | fb-messenger-platform-20150218 49 | fb-messenger-platform-20150305 50 | 51 | CFBundleURLTypes 52 | 53 | 54 | CFBundleURLSchemes 55 | 56 | fb{{facebook.appid}} 57 | 58 | 59 | 60 | FacebookAppID 61 | {{facebook.appid}} 62 | FacebookClientToken 63 | {{facebook.clienttoken}} 64 | FacebookDisplayName 65 | {{project.title}} 66 | 67 | 68 | -------------------------------------------------------------------------------- /facebook/manifests/ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | pod 'FBSDKCoreKit', '17.0.0' 3 | pod 'FBSDKLoginKit', '17.0.0' 4 | pod 'FBSDKShareKit', '17.0.0' 5 | pod 'FBSDKGamingServicesKit', '17.0.0' 6 | -------------------------------------------------------------------------------- /facebook/manifests/web/engine_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /facebook/misc/README_DEBUG_LOCAL.md: -------------------------------------------------------------------------------- 1 | # Test local app 2 | 3 | It is beneficial to test your app locally, before publishing it. 4 | Here we describe how to do that. 5 | 6 | ## Facebook 7 | 8 | ### Create a FB Application 9 | 10 | * Go to [developers.facebook.com](http://developers.facebook.com) and create an application. 11 | 12 | * Make note of the `Application ID` 13 | 14 | ### Settings 15 | 16 | * Under `Settings` -> `Basic` -> `App Domains`, set the value `localhost` 17 | 18 | * At the bottom of the page, press `Add Platform` and add `Facebook Web Games` 19 | 20 | * Under `Website` -> `Site URL` set 21 | 22 | https://localhost:8000/index.html? 23 | 24 | * Press `Save Changes` at the bottom of the page 25 | 26 | ### Facebook Login 27 | 28 | * For the application, under `Products` add the `Facebook Login` application. 29 | 30 | * Under `Client OAuth Settings` -> `Valid OAuth Redirect URIs` set the value: 31 | 32 | https://localhost:8000 33 | 34 | 35 | ## Bundle your game 36 | 37 | * Add the `Application ID` to your game.project: 38 | 39 | [facebook] 40 | appid = YOUR_APPLICATION_ID 41 | 42 | * Bundle the game to HTML5 43 | 44 | ## HTTPS server 45 | 46 | * Open a terminal/console window in your bundled HTML5 game folder (where `index.html` is): 47 | 48 | $ cd path_to_my_game/ 49 | 50 | * Make sure you have the script `misc/facebook_server.py` available locally 51 | 52 | * A `key.pem` and `cert.pem` are required for the next step. You can either run [create_cert.sh](./facebook/misc/create_cert.sh) or run: 53 | 54 | openssl req -x509 -newkey rsa:2048 -days 30 -subj "/CN=Test Team" -nodes -keyout "key.pem" -out "cert.pem" > 55 | /dev/null 2>&1 56 | 57 | 58 | * Launch the https server: 59 | 60 | $ python ~/facebook_server.py 8000 61 | 62 | Watch the output carefully, noticing if it launched the https server properly. 63 | If it didn't find the cert, it will launch a http server. 64 | 65 | E.g., if you are continually rebundling the game, it's beneficial to have the certs elsewhere: 66 | 67 | $ (cd ~/bundles/myapp/ && python ~/projects/extension-facebook/facebook/misc/facebook_server.py 8000 ~/cert.pem ~/key.pem) 68 | 69 | ## Test the app 70 | 71 | * Go to your app page: https://apps.facebook.com/ 72 | -------------------------------------------------------------------------------- /facebook/misc/create_cert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | openssl req -x509 -newkey rsa:2048 -days 30 -subj "/CN=Defold Team" \ 5 | -nodes -keyout "key.pem" -out "cert.pem" \ 6 | > /dev/null 2>&1 7 | -------------------------------------------------------------------------------- /facebook/misc/facebook_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import string, cgi, time, os, ssl, sys 4 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 5 | 6 | class MyRequestHandler(BaseHTTPRequestHandler): 7 | 8 | def do_GET(self): 9 | try: 10 | if "hub.verify_token" in self.path: 11 | # fb test hooks 12 | self.send_response(200) 13 | self.send_header("Content-type", "text/html") 14 | self.end_headers() 15 | querys = dict(cgi.parse_qsl(self.path)) 16 | verify_token = "tokentest" 17 | if querys["hub.verify_token"] == verify_token: 18 | self.wfile.write(str(querys["hub.challenge"])) 19 | return 20 | 21 | if self.path.endswith("/postwebhook"): 22 | self.send_response(200) 23 | self.send_header("Content-type", "text/html") 24 | self.end_headers() 25 | self.wfile.write("ok") 26 | return 27 | 28 | # your logic for this URL here, such as executing a bash script 29 | file_path = os.path.abspath(self.path.lstrip("/")) 30 | if os.path.isfile(file_path): 31 | self.send_response(200) 32 | self.send_header("Content-type", "text/html") 33 | self.end_headers() 34 | f=open(file_path) 35 | self.wfile.write(f.read()) 36 | f.close() 37 | return 38 | 39 | else: 40 | self.send_error(404, "File Not Found %s" % self.path) 41 | 42 | except: 43 | self.send_error(404, "File Not Found %s" % self.path) 44 | 45 | 46 | def do_POST(self): 47 | try: 48 | if self.headers["Content-type"] == "application/json": 49 | self.send_response(200) 50 | length = int(self.headers.getheader('content-length')) 51 | print 'content-data:', str(self.rfile.read(length)) 52 | return 53 | except: 54 | pass 55 | self.do_GET() 56 | 57 | 58 | def main(port, cert=None, key=None): 59 | try: 60 | server = HTTPServer(("", port), MyRequestHandler) 61 | is_https = False 62 | if cert is not None and key is not None: 63 | if os.path.exists(cert) and os.path.exists(key): 64 | server.socket = ssl.wrap_socket(server.socket, certfile=cert, keyfile=key, server_side = True) 65 | is_https = True 66 | else: 67 | print "Couldn't find cert.pem and/or key.pem, falling back to http://" 68 | print "Server started at " + ("https" if is_https else "http") + "://localhost:%d" % port 69 | print "Press ^C to quit." 70 | server.serve_forever() 71 | except KeyboardInterrupt: 72 | print " received, shutting down server" 73 | 74 | server.socket.close() 75 | 76 | if __name__ == "__main__": 77 | port = 8000 78 | if len(sys.argv) >= 2: 79 | port = int(sys.argv[1]) 80 | 81 | cert = "cert.pem" 82 | if len(sys.argv) >= 3: 83 | cert = sys.argv[2] 84 | 85 | key = "key.pem" 86 | if len(sys.argv) >= 4: 87 | key = sys.argv[3] 88 | 89 | main(port, cert, key) 90 | -------------------------------------------------------------------------------- /facebook/src/facebook.cpp: -------------------------------------------------------------------------------- 1 | #if defined(DM_PLATFORM_ANDROID) || defined(DM_PLATFORM_IOS) || defined(DM_PLATFORM_HTML5) 2 | 3 | #include "facebook_private.h" 4 | #include "facebook_analytics.h" 5 | #include 6 | #include 7 | 8 | #define MODULE_NAME "facebook" // the c++ module name from ext.manifest, also used as the Lua module name 9 | 10 | namespace dmFacebook { 11 | 12 | #define CHECK_FACEBOOK_INIT(_L_) \ 13 | if (!Platform_FacebookInitialized()) return luaL_error(_L_, "Facebook has not been initialized, is facebook.appid set in game.project?"); 14 | 15 | static int Facebook_AccessToken(lua_State* L) 16 | { 17 | CHECK_FACEBOOK_INIT(L); 18 | return Platform_FacebookAccessToken(L); 19 | } 20 | 21 | static int Facebook_Logout(lua_State* L) 22 | { 23 | CHECK_FACEBOOK_INIT(L); 24 | return Platform_FacebookLogout(L); 25 | } 26 | 27 | static int Facebook_Permissions(lua_State* L) 28 | { 29 | CHECK_FACEBOOK_INIT(L); 30 | return Platform_FacebookPermissions(L); 31 | } 32 | 33 | static int Facebook_PostEvent(lua_State* L) 34 | { 35 | CHECK_FACEBOOK_INIT(L); 36 | return Platform_FacebookPostEvent(L); 37 | } 38 | 39 | static int Facebook_EnableEventUsage(lua_State* L) 40 | { 41 | CHECK_FACEBOOK_INIT(L); 42 | return Platform_FacebookEnableEventUsage(L); 43 | } 44 | 45 | static int Facebook_DisableEventUsage(lua_State* L) 46 | { 47 | CHECK_FACEBOOK_INIT(L); 48 | return Platform_FacebookDisableEventUsage(L); 49 | } 50 | 51 | static int Facebook_ShowDialog(lua_State* L) 52 | { 53 | CHECK_FACEBOOK_INIT(L); 54 | return Platform_FacebookShowDialog(L); 55 | } 56 | 57 | static int Facebook_LoginWithPermissions(lua_State* L) 58 | { 59 | DM_LUA_STACK_CHECK(L, 0); 60 | CHECK_FACEBOOK_INIT(L); 61 | 62 | luaL_checktype(L, 1, LUA_TTABLE); 63 | luaL_checktype(L, 2, LUA_TNUMBER); 64 | dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 3); 65 | 66 | char* permissions[128]; 67 | int permission_count = luaTableToCArray(L, 1, permissions, sizeof(permissions) / sizeof(permissions[0])); 68 | if (permission_count == -1) 69 | { 70 | return luaL_error(L, "Facebook permissions must be strings"); 71 | } 72 | 73 | int audience = luaL_checkinteger(L, 2); 74 | 75 | Platform_FacebookLoginWithPermissions(L, (const char**) permissions, permission_count, audience, callback); 76 | 77 | for (int i = 0; i < permission_count; ++i) { 78 | free(permissions[i]); 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | static int Facebook_GetVersion(lua_State* L) 85 | { 86 | DM_LUA_STACK_CHECK(L, 1); 87 | CHECK_FACEBOOK_INIT(L); 88 | 89 | const char* version = Platform_GetVersion(); 90 | if (!version) 91 | { 92 | return luaL_error(L, "get_version not supported"); 93 | } 94 | lua_pushstring(L, version); 95 | free((void*)version); 96 | 97 | return 1; 98 | } 99 | 100 | static int Facebook_FetchDeferredAppLinkData(lua_State* L) 101 | { 102 | DM_LUA_STACK_CHECK(L, 0); 103 | CHECK_FACEBOOK_INIT(L); 104 | 105 | dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 1); 106 | 107 | Platform_FetchDeferredAppLinkData(L, callback); 108 | 109 | return 0; 110 | } 111 | 112 | static int Facebook_Init(lua_State* L) 113 | { 114 | DM_LUA_STACK_CHECK(L, 0); 115 | 116 | if (Platform_FacebookInitialized()) 117 | { 118 | return luaL_error(L, "Facebook has already been initialized"); 119 | } 120 | Platform_FacebookInit(L); 121 | 122 | return 0; 123 | } 124 | 125 | static int Facebook_EnableAdvertiserTracking(lua_State* L) 126 | { 127 | #if defined(DM_PLATFORM_IOS) 128 | CHECK_FACEBOOK_INIT(L); 129 | return Platform_FacebookEnableAdvertiserTracking(L); 130 | #else 131 | return 0; 132 | #endif 133 | } 134 | 135 | static int Facebook_DisableAdvertiserTracking(lua_State* L) 136 | { 137 | #if defined(DM_PLATFORM_IOS) 138 | CHECK_FACEBOOK_INIT(L); 139 | return Platform_FacebookDisableAdvertiserTracking(L); 140 | #else 141 | return 0; 142 | #endif 143 | } 144 | 145 | static int Facebook_SetDefaultAudience(lua_State* L) 146 | { 147 | #if defined(DM_PLATFORM_IOS) 148 | CHECK_FACEBOOK_INIT(L); 149 | DM_LUA_STACK_CHECK(L, 0); 150 | int audience = luaL_checkinteger(L, 1); 151 | Platform_FacebookSetDefaultAudience(audience); 152 | return 0; 153 | #else 154 | dmLogWarning("`set_default_audience` is iOS only function."); 155 | return 0; 156 | #endif 157 | } 158 | 159 | static int Facebook_LoginWithTrackingPreference(lua_State* L) 160 | { 161 | #if defined(DM_PLATFORM_IOS) 162 | CHECK_FACEBOOK_INIT(L); 163 | DM_LUA_STACK_CHECK(L, 0); 164 | 165 | int login_tracking = luaL_checkinteger(L, 1); 166 | luaL_checktype(L, 2, LUA_TTABLE); 167 | const char* crypto_nonce = luaL_checkstring(L, 3); 168 | dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 4); 169 | 170 | char* permissions[128]; 171 | int permission_count = luaTableToCArray(L, 2, permissions, sizeof(permissions) / sizeof(permissions[0])); 172 | if (permission_count == -1) 173 | { 174 | return luaL_error(L, "Facebook permissions must be strings"); 175 | } 176 | 177 | Platform_FacebookLoginWithTrackingPreference((dmFacebook::LoginTracking)login_tracking, (const char**) permissions, permission_count, crypto_nonce, callback); 178 | 179 | for (int i = 0; i < permission_count; ++i) 180 | { 181 | free(permissions[i]); 182 | } 183 | return 0; 184 | #else 185 | dmLogWarning("`login_with_tracking_preference` is iOS only function."); 186 | return 0; 187 | #endif 188 | } 189 | 190 | static int Facebook_GetCurrentAuthenticationToken(lua_State* L) 191 | { 192 | #if defined(DM_PLATFORM_IOS) 193 | CHECK_FACEBOOK_INIT(L); 194 | DM_LUA_STACK_CHECK(L, 1); 195 | const char* token = Platform_FacebookGetCurrentAuthenticationToken(); 196 | if (token == 0x0) 197 | { 198 | lua_pushnil(L); 199 | } 200 | else 201 | { 202 | lua_pushstring(L, token); 203 | } 204 | return 1; 205 | #else 206 | dmLogWarning("`get_current_authentication_token` is iOS only function."); 207 | return 0; 208 | #endif 209 | } 210 | 211 | static int Facebook_GetCurrentProfile(lua_State* L) 212 | { 213 | #if defined(DM_PLATFORM_IOS) 214 | CHECK_FACEBOOK_INIT(L); 215 | DM_LUA_STACK_CHECK(L, 1); 216 | return Platform_FacebookGetCurrentProfile(L); 217 | #else 218 | dmLogWarning("`get_current_profile` is iOS only function."); 219 | return 0; 220 | #endif 221 | } 222 | 223 | static const luaL_reg Facebook_methods[] = 224 | { 225 | {"init", Facebook_Init}, 226 | {"get_version", Facebook_GetVersion}, 227 | {"logout", Facebook_Logout}, 228 | {"access_token", Facebook_AccessToken}, 229 | {"permissions", Facebook_Permissions}, 230 | {"post_event", Facebook_PostEvent}, 231 | {"enable_event_usage", Facebook_EnableEventUsage}, 232 | {"disable_event_usage", Facebook_DisableEventUsage}, 233 | {"show_dialog", Facebook_ShowDialog}, 234 | {"login_with_permissions", Facebook_LoginWithPermissions}, 235 | {"deferred_deep_link", Facebook_FetchDeferredAppLinkData}, 236 | {"enable_advertiser_tracking", Facebook_EnableAdvertiserTracking}, 237 | {"disable_advertiser_tracking", Facebook_DisableAdvertiserTracking}, 238 | // iOS only function. FB SDK 17.0: 239 | {"set_default_audience", Facebook_SetDefaultAudience}, 240 | {"login_with_tracking_preference", Facebook_LoginWithTrackingPreference}, 241 | {"get_current_authentication_token", Facebook_GetCurrentAuthenticationToken}, 242 | {"get_current_profile", Facebook_GetCurrentProfile}, 243 | {0, 0} 244 | }; 245 | 246 | static void LuaInit(lua_State* L) 247 | { 248 | int top = lua_gettop(L); 249 | luaL_register(L, MODULE_NAME, Facebook_methods); 250 | 251 | #define SETCONSTANT(name, val) \ 252 | lua_pushnumber(L, (lua_Number) val); \ 253 | lua_setfield(L, -2, #name);\ 254 | 255 | SETCONSTANT(STATE_CREATED, dmFacebook::STATE_CREATED); 256 | SETCONSTANT(STATE_CREATED_TOKEN_LOADED, dmFacebook::STATE_CREATED_TOKEN_LOADED); 257 | SETCONSTANT(STATE_CREATED_OPENING, dmFacebook::STATE_CREATED_OPENING); 258 | SETCONSTANT(STATE_OPEN, dmFacebook::STATE_OPEN); 259 | SETCONSTANT(STATE_OPEN_TOKEN_EXTENDED, dmFacebook::STATE_OPEN_TOKEN_EXTENDED); 260 | SETCONSTANT(STATE_CLOSED, dmFacebook::STATE_CLOSED); 261 | SETCONSTANT(STATE_CLOSED_LOGIN_FAILED, dmFacebook::STATE_CLOSED_LOGIN_FAILED); 262 | 263 | SETCONSTANT(GAMEREQUEST_ACTIONTYPE_NONE, dmFacebook::GAMEREQUEST_ACTIONTYPE_NONE); 264 | SETCONSTANT(GAMEREQUEST_ACTIONTYPE_SEND, dmFacebook::GAMEREQUEST_ACTIONTYPE_SEND); 265 | SETCONSTANT(GAMEREQUEST_ACTIONTYPE_ASKFOR, dmFacebook::GAMEREQUEST_ACTIONTYPE_ASKFOR); 266 | SETCONSTANT(GAMEREQUEST_ACTIONTYPE_TURN, dmFacebook::GAMEREQUEST_ACTIONTYPE_TURN); 267 | 268 | SETCONSTANT(GAMEREQUEST_FILTER_NONE, dmFacebook::GAMEREQUEST_FILTER_NONE); 269 | SETCONSTANT(GAMEREQUEST_FILTER_APPUSERS, dmFacebook::GAMEREQUEST_FILTER_APPUSERS); 270 | SETCONSTANT(GAMEREQUEST_FILTER_APPNONUSERS, dmFacebook::GAMEREQUEST_FILTER_APPNONUSERS); 271 | 272 | SETCONSTANT(AUDIENCE_NONE, dmFacebook::AUDIENCE_NONE); 273 | SETCONSTANT(AUDIENCE_ONLYME, dmFacebook::AUDIENCE_ONLYME); 274 | SETCONSTANT(AUDIENCE_FRIENDS, dmFacebook::AUDIENCE_FRIENDS); 275 | SETCONSTANT(AUDIENCE_EVERYONE, dmFacebook::AUDIENCE_EVERYONE); 276 | 277 | SETCONSTANT(LOGIN_TRACKING_LIMITED, dmFacebook::LOGIN_TRACKING_LIMITED); 278 | SETCONSTANT(LOGIN_TRACKING_ENABLED, dmFacebook::LOGIN_TRACKING_ENABLED); 279 | 280 | #undef SETCONSTANT 281 | 282 | #if defined(DM_PLATFORM_HTML5) 283 | lua_pushstring(L, dmFacebook::GRAPH_API_VERSION); 284 | lua_setfield(L, -2, "GRAPH_API_VERSION"); 285 | #endif 286 | 287 | dmFacebook::Analytics::RegisterConstants(L, MODULE_NAME); 288 | 289 | lua_pop(L, 1); 290 | assert(top == lua_gettop(L)); 291 | } 292 | 293 | } // namespace 294 | 295 | static dmExtension::Result AppInitializeFacebook(dmExtension::AppParams* params) 296 | { 297 | const char* app_id = dmConfigFile::GetString(params->m_ConfigFile, "facebook.appid", 0); 298 | if( !app_id ) 299 | { 300 | dmLogDebug("No facebook.appid. Disabling module"); 301 | return dmExtension::RESULT_OK; 302 | } 303 | return Platform_AppInitializeFacebook(params, app_id); 304 | } 305 | 306 | static dmExtension::Result AppFinalizeFacebook(dmExtension::AppParams* params) 307 | { 308 | return Platform_AppFinalizeFacebook(params); 309 | } 310 | 311 | static dmExtension::Result InitializeFacebook(dmExtension::Params* params) 312 | { 313 | dmFacebook::LuaInit(params->m_L); 314 | dmExtension::Result r = Platform_InitializeFacebook(params); 315 | if (r == dmExtension::RESULT_OK) 316 | { 317 | const int autoinit = dmConfigFile::GetInt(params->m_ConfigFile, "facebook.autoinit", 1); 318 | if (autoinit) 319 | { 320 | dmFacebook::Facebook_Init(params->m_L); 321 | } 322 | } 323 | return r; 324 | } 325 | 326 | static dmExtension::Result FinalizeFacebook(dmExtension::Params* params) 327 | { 328 | return Platform_FinalizeFacebook(params); 329 | } 330 | 331 | static dmExtension::Result UpdateFacebook(dmExtension::Params* params) 332 | { 333 | return Platform_UpdateFacebook(params); 334 | } 335 | 336 | static void OnEventFacebook(dmExtension::Params* params, const dmExtension::Event* event) 337 | { 338 | Platform_OnEventFacebook(params, event); 339 | } 340 | 341 | DM_DECLARE_EXTENSION(FacebookExtExternal, "Facebook", AppInitializeFacebook, AppFinalizeFacebook, InitializeFacebook, UpdateFacebook, OnEventFacebook, FinalizeFacebook) 342 | 343 | #else 344 | 345 | extern "C" void FacebookExtExternal() 346 | { 347 | } 348 | 349 | #endif 350 | -------------------------------------------------------------------------------- /facebook/src/facebook_analytics.cpp: -------------------------------------------------------------------------------- 1 | #include "facebook_analytics.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include "facebook_private.h" 9 | 10 | namespace 11 | { 12 | const char* EVENT_TABLE[dmFacebook::Analytics::MAX_NUM_EVENTS] = { 13 | "fb_mobile_level_achieved", 14 | "fb_mobile_activate_app", 15 | "fb_mobile_add_payment_info", 16 | "fb_mobile_add_to_cart", 17 | "fb_mobile_add_to_wishlist", 18 | "fb_mobile_complete_registration", 19 | "fb_mobile_tutorial_completion", 20 | "fb_mobile_deactivate_app", 21 | "fb_mobile_initiated_checkout", 22 | "fb_mobile_purchase", 23 | "fb_mobile_rate", 24 | "fb_mobile_search", 25 | "fb_mobile_app_interruptions", 26 | "fb_mobile_spent_credits", 27 | "fb_mobile_time_between_sessions", 28 | "fb_mobile_achievement_unlocked", 29 | "fb_mobile_content_view" 30 | }; 31 | 32 | const char* PARAMETER_TABLE[dmFacebook::Analytics::MAX_NUM_PARAMS] = { 33 | "fb_content_id", 34 | "fb_content_type", 35 | "fb_currency", 36 | "fb_description", 37 | "fb_level", 38 | "fb_max_rating_value", 39 | "fb_num_items", 40 | "fb_payment_info_available", 41 | "fb_registration_method", 42 | "fb_search_string", 43 | "fb_mobile_launch_source", 44 | "fb_success" 45 | }; 46 | 47 | const char* LookupEvent(unsigned int index) 48 | { 49 | if (index < dmFacebook::Analytics::MAX_NUM_EVENTS) { 50 | return ::EVENT_TABLE[index]; 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | const char* LookupParameter(unsigned int index) 57 | { 58 | if (index < dmFacebook::Analytics::MAX_NUM_PARAMS) 59 | { 60 | return ::PARAMETER_TABLE[index]; 61 | } 62 | 63 | return 0; 64 | } 65 | }; 66 | 67 | const char* dmFacebook::Analytics::GetEvent(lua_State* L, int index) 68 | { 69 | const char* event = 0; 70 | if (lua_isnil(L, index)) 71 | { 72 | luaL_argerror(L, index, "Facebook Analytics event cannot be nil"); 73 | } 74 | else if (lua_isnumber(L, index)) 75 | { 76 | unsigned int event_number = (unsigned int) luaL_checknumber(L, index); 77 | event = ::LookupEvent(event_number); 78 | if (event == 0) 79 | { 80 | luaL_argerror(L, index, "Facebook Analytics event does not exist"); 81 | } 82 | } 83 | else if (lua_isstring(L, index)) 84 | { 85 | size_t len = 0; 86 | event = luaL_checklstring(L, index, &len); 87 | if (len == 0) 88 | { 89 | luaL_argerror(L, index, "Facebook Analytics event cannot be empty"); 90 | } 91 | } 92 | else 93 | { 94 | luaL_argerror(L, index, 95 | "Facebook Analytics event must be number or string"); 96 | } 97 | 98 | return event; 99 | } 100 | 101 | const char* dmFacebook::Analytics::GetParameter(lua_State* L, int index, int tableIndex) 102 | { 103 | const char* parameter = 0; 104 | if (lua_isnil(L, index)) 105 | { 106 | luaL_argerror(L, tableIndex, "Facebook Analytics parameter cannot be nil"); 107 | } 108 | else if (lua_isnumber(L, index)) 109 | { 110 | unsigned int parameter_number = 111 | (unsigned int) luaL_checknumber(L, index); 112 | parameter = ::LookupParameter(parameter_number); 113 | if (parameter == 0) 114 | { 115 | luaL_argerror(L, tableIndex, 116 | "Facebook Analytics parameter does not exist"); 117 | } 118 | } 119 | else if (lua_isstring(L, index)) 120 | { 121 | size_t len = 0; 122 | parameter = luaL_checklstring(L, index, &len); 123 | if (len == 0) 124 | { 125 | luaL_argerror(L, tableIndex, 126 | "Facebook Analytics parameter cannot be empty"); 127 | } 128 | } 129 | else 130 | { 131 | luaL_argerror(L, tableIndex, 132 | "Facebook Analytics parameter must be number or string"); 133 | } 134 | 135 | return parameter; 136 | } 137 | 138 | void dmFacebook::Analytics::GetParameterTable(lua_State* L, int index, const char** keys, 139 | const char** values, unsigned int* length) 140 | { 141 | lua_pushvalue(L, index); 142 | lua_pushnil(L); 143 | 144 | unsigned int position = 0; 145 | while (lua_next(L, -2) && position < (*length)) 146 | { 147 | lua_pushvalue(L, -2); 148 | keys[position] = dmFacebook::Analytics::GetParameter(L, -1, index); 149 | values[position] = lua_tostring(L, -2); 150 | lua_pop(L, 2); 151 | 152 | if (keys[position] == 0x0) 153 | { 154 | dmLogError("Unsupported parameter type for key, must be string or number."); 155 | } 156 | else if (values[position] == 0x0) 157 | { 158 | dmLogError("Unsupported parameter value type for key '%s', value must be string or number.", keys[position]); 159 | } 160 | else 161 | { 162 | ++position; 163 | } 164 | } 165 | 166 | lua_pop(L, 1); 167 | *length = position; 168 | } 169 | 170 | void dmFacebook::Analytics::RegisterConstants(lua_State* L, const char* modulename) 171 | { 172 | // Add constants to table 173 | lua_getglobal(L, modulename); 174 | 175 | #define SETCONSTANT(name, val) \ 176 | lua_pushnumber(L, (lua_Number) val); lua_setfield(L, -2, #name); 177 | 178 | SETCONSTANT(EVENT_ACHIEVED_LEVEL, dmFacebook::Analytics::ACHIEVED_LEVEL); 179 | // SETCONSTANT(EVENT_ACTIVATED_APP, dmFacebook::Analytics::ACTIVATED_APP); 180 | SETCONSTANT(EVENT_ADDED_PAYMENT_INFO, dmFacebook::Analytics::ADDED_PAYMENT_INFO); 181 | SETCONSTANT(EVENT_ADDED_TO_CART, dmFacebook::Analytics::ADDED_TO_CART); 182 | SETCONSTANT(EVENT_ADDED_TO_WISHLIST, dmFacebook::Analytics::ADDED_TO_WISHLIST); 183 | SETCONSTANT(EVENT_COMPLETED_REGISTRATION, dmFacebook::Analytics::COMPLETED_REGISTRATION); 184 | SETCONSTANT(EVENT_COMPLETED_TUTORIAL, dmFacebook::Analytics::COMPLETED_TUTORIAL); 185 | // SETCONSTANT(EVENT_DEACTIVATED_APP, dmFacebook::Analytics::DEACTIVATED_APP); 186 | SETCONSTANT(EVENT_INITIATED_CHECKOUT, dmFacebook::Analytics::INITIATED_CHECKOUT); 187 | SETCONSTANT(EVENT_PURCHASED, dmFacebook::Analytics::PURCHASED); 188 | SETCONSTANT(EVENT_RATED, dmFacebook::Analytics::RATED); 189 | SETCONSTANT(EVENT_SEARCHED, dmFacebook::Analytics::SEARCHED); 190 | // SETCONSTANT(EVENT_SESSION_INTERRUPTIONS, dmFacebook::Analytics::SESSION_INTERRUPTIONS); 191 | SETCONSTANT(EVENT_SPENT_CREDITS, dmFacebook::Analytics::SPENT_CREDITS); 192 | SETCONSTANT(EVENT_TIME_BETWEEN_SESSIONS, dmFacebook::Analytics::TIME_BETWEEN_SESSIONS); 193 | SETCONSTANT(EVENT_UNLOCKED_ACHIEVEMENT, dmFacebook::Analytics::UNLOCKED_ACHIEVEMENT); 194 | SETCONSTANT(EVENT_VIEWED_CONTENT, dmFacebook::Analytics::VIEWED_CONTENT); 195 | 196 | SETCONSTANT(PARAM_CONTENT_ID, dmFacebook::Analytics::CONTENT_ID); 197 | SETCONSTANT(PARAM_CONTENT_TYPE, dmFacebook::Analytics::CONTENT_TYPE); 198 | SETCONSTANT(PARAM_CURRENCY, dmFacebook::Analytics::CURRENCY); 199 | SETCONSTANT(PARAM_DESCRIPTION, dmFacebook::Analytics::DESCRIPTION); 200 | SETCONSTANT(PARAM_LEVEL, dmFacebook::Analytics::LEVEL); 201 | SETCONSTANT(PARAM_MAX_RATING_VALUE, dmFacebook::Analytics::MAX_RATING_VALUE); 202 | SETCONSTANT(PARAM_NUM_ITEMS, dmFacebook::Analytics::NUM_ITEMS); 203 | SETCONSTANT(PARAM_PAYMENT_INFO_AVAILABLE, dmFacebook::Analytics::PAYMENT_INFO_AVAILABLE); 204 | SETCONSTANT(PARAM_REGISTRATION_METHOD, dmFacebook::Analytics::REGISTRATION_METHOD); 205 | SETCONSTANT(PARAM_SEARCH_STRING, dmFacebook::Analytics::SEARCH_STRING); 206 | SETCONSTANT(PARAM_SOURCE_APPLICATION, dmFacebook::Analytics::SOURCE_APPLICATION); 207 | SETCONSTANT(PARAM_SUCCESS, dmFacebook::Analytics::SUCCESS); 208 | 209 | #undef SETCONSTANT 210 | 211 | // Pop table LIB_NAME 212 | lua_pop(L, 1); 213 | 214 | } 215 | -------------------------------------------------------------------------------- /facebook/src/facebook_analytics.h: -------------------------------------------------------------------------------- 1 | #ifndef H_FACEBOOK_ANALYTICS 2 | #define H_FACEBOOK_ANALYTICS 3 | 4 | extern "C" 5 | { 6 | #include 7 | #include 8 | } 9 | 10 | namespace dmFacebook { 11 | 12 | namespace Analytics { 13 | 14 | /** 15 | * \brief The maximum number of parameters that can be sent with an 16 | * event to Facebook Analytics. 17 | * 18 | * \see https://developers.facebook.com/docs/app-events 19 | */ 20 | const unsigned int MAX_PARAMS = 25; 21 | 22 | /** 23 | * \brief The internal representation of the predefined events for 24 | * Facebook Analytics. 25 | * 26 | * \note This list has to match the list in facebook_analytics.cpp 27 | */ 28 | enum Event { 29 | ACHIEVED_LEVEL = 0, 30 | ACTIVATED_APP = 1, 31 | ADDED_PAYMENT_INFO = 2, 32 | ADDED_TO_CART = 3, 33 | ADDED_TO_WISHLIST = 4, 34 | COMPLETED_REGISTRATION = 5, 35 | COMPLETED_TUTORIAL = 6, 36 | DEACTIVATED_APP = 7, 37 | INITIATED_CHECKOUT = 8, 38 | PURCHASED = 9, 39 | RATED = 10, 40 | SEARCHED = 11, 41 | SESSION_INTERRUPTIONS = 12, 42 | SPENT_CREDITS = 13, 43 | TIME_BETWEEN_SESSIONS = 14, 44 | UNLOCKED_ACHIEVEMENT = 15, 45 | VIEWED_CONTENT = 16, 46 | MAX_NUM_EVENTS = 17 47 | }; 48 | 49 | /** 50 | * \brief The internal representation of the predefined parameters for 51 | * Facebook Analytics. 52 | * 53 | * \note This list has to match the list in facebook_analytics.cpp. 54 | */ 55 | enum Parameter { 56 | CONTENT_ID = 0, 57 | CONTENT_TYPE = 1, 58 | CURRENCY = 2, 59 | DESCRIPTION = 3, 60 | LEVEL = 4, 61 | MAX_RATING_VALUE = 5, 62 | NUM_ITEMS = 6, 63 | PAYMENT_INFO_AVAILABLE = 7, 64 | REGISTRATION_METHOD = 8, 65 | SEARCH_STRING = 9, 66 | SOURCE_APPLICATION = 10, 67 | SUCCESS = 11, 68 | MAX_NUM_PARAMS = 12 69 | }; 70 | 71 | /** 72 | * \brief Get the event identifier (Facebook SDK string or custom) 73 | * based on the argument on the Lua stack provided specified by index. 74 | * 75 | * \param L The Lua stack. 76 | * \param index The position on the stack where the event can be found. 77 | * 78 | * \exception If the event is neither represented as a string or a 79 | * number an argerror will be thrown. If the event is represented as a 80 | * number that doesn't exist an argerror will be thrown. If the event 81 | * is represented as a string that is empty an argerror will be thrown. 82 | * 83 | * \return An event identifier to be used with Facebook Analytics. 84 | */ 85 | const char* GetEvent(lua_State* L, int index); 86 | 87 | /** 88 | * \brief Get the parameter identifier (Facebook SDK string or custom) 89 | * based on the argument on the Lua stack provided specified by index. 90 | * 91 | * \param L The Lua stack. 92 | * \param index The position on the stack where the parameter can be 93 | * found. 94 | * \param tableIndex The absolute position on the stack where the table 95 | * that contains all parameters are stored. 96 | * 97 | * \exception If the parameter is neither represented as a string or a 98 | * number an argerror will be thrown. If the event is represented as a 99 | * number that doesn't exist an argerror will be thrown. If the event 100 | * is represented as a string that is empty an argerror will be thrown. 101 | * 102 | * \return A parameter identifier to be used with Facebook Analytics. 103 | */ 104 | const char* GetParameter(lua_State* L, int index, int tableIndex); 105 | 106 | /** 107 | * \brief Get the values from a table on the Lua stack at the position 108 | * specified by index and populate the arrays keys and values with the 109 | * table content. 110 | * 111 | * \param L The Lua stack. 112 | * \param index The position on the stack where the table can be found. 113 | * \param keys An array that should be populated with keys from the 114 | * table. 115 | * \param values An array that should be populated with the values from 116 | * the table. 117 | * \param length The length of the arrays keys and values. This 118 | * parameter will be populated with the number of items that were put 119 | * into the arrays keys and values. 120 | */ 121 | void GetParameterTable(lua_State* L, int index, const char** keys, 122 | const char** values, unsigned int* length); 123 | 124 | /** 125 | * \brief Push constants for predefined events and predefined paramters 126 | * onto the stack L. 127 | * 128 | * \param L The Lua stack. 129 | */ 130 | void RegisterConstants(lua_State* L, const char* modulename); 131 | 132 | }; 133 | 134 | }; 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /facebook/src/facebook_android.cpp: -------------------------------------------------------------------------------- 1 | #if defined(DM_PLATFORM_ANDROID) 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "facebook_private.h" 17 | #include "facebook_util.h" 18 | #include "facebook_analytics.h" 19 | 20 | extern struct android_app* g_AndroidApp; 21 | 22 | struct Facebook 23 | { 24 | Facebook() 25 | { 26 | memset(this, 0, sizeof(*this)); 27 | } 28 | 29 | jobject m_FB; 30 | jmethodID m_GetSdkVersion; 31 | jmethodID m_Logout; 32 | jmethodID m_IteratePermissions; 33 | jmethodID m_GetAccessToken; 34 | jmethodID m_ShowDialog; 35 | jmethodID m_LoginWithPermissions; 36 | jmethodID m_FetchDeferredAppLink; 37 | 38 | jmethodID m_PostEvent; 39 | jmethodID m_EnableEventUsage; 40 | jmethodID m_DisableEventUsage; 41 | 42 | jobject m_FBApp; 43 | jmethodID m_Activate; 44 | jmethodID m_Deactivate; 45 | 46 | const char* m_AppId; 47 | int m_RefCount; // depending if we have a shared state or not 48 | int m_DisableFaceBookEvents; 49 | 50 | dmFacebook::CommandQueue m_CommandQueue; 51 | }; 52 | 53 | static Facebook g_Facebook; 54 | 55 | static const char* StrDup(JNIEnv* env, jstring s) 56 | { 57 | if (s != NULL) 58 | { 59 | const char* str = env->GetStringUTFChars(s, 0); 60 | const char* dup = strdup(str); 61 | env->ReleaseStringUTFChars(s, str); 62 | return dup; 63 | } 64 | else 65 | { 66 | return 0x0; 67 | } 68 | } 69 | 70 | #ifdef __cplusplus 71 | extern "C" { 72 | #endif 73 | 74 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onLogin 75 | (JNIEnv* env, jobject, jlong userData, jint state, jstring error) 76 | { 77 | dmFacebook::FacebookCommand cmd; 78 | cmd.m_Type = dmFacebook::COMMAND_TYPE_LOGIN; 79 | cmd.m_State = (int)state; 80 | cmd.m_Callback = (dmScript::LuaCallbackInfo*)userData; 81 | cmd.m_Error = StrDup(env, error); 82 | dmFacebook::QueuePush(&g_Facebook.m_CommandQueue, &cmd); 83 | } 84 | 85 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onLoginWithPermissions 86 | (JNIEnv* env, jobject, jlong userData, jint state, jstring error) 87 | { 88 | dmFacebook::FacebookCommand cmd; 89 | 90 | cmd.m_Type = dmFacebook::COMMAND_TYPE_LOGIN_WITH_PERMISSIONS; 91 | cmd.m_State = (int) state; 92 | cmd.m_Callback = (dmScript::LuaCallbackInfo*)userData; 93 | cmd.m_Error = StrDup(env, error); 94 | 95 | dmFacebook::QueuePush(&g_Facebook.m_CommandQueue, &cmd); 96 | } 97 | 98 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onRequestRead 99 | (JNIEnv* env, jobject, jlong userData, jstring error) 100 | { 101 | dmFacebook::FacebookCommand cmd; 102 | cmd.m_Type = dmFacebook::COMMAND_TYPE_REQUEST_READ; 103 | cmd.m_Callback = (dmScript::LuaCallbackInfo*)userData; 104 | cmd.m_Error = StrDup(env, error); 105 | dmFacebook::QueuePush(&g_Facebook.m_CommandQueue, &cmd); 106 | } 107 | 108 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onRequestPublish 109 | (JNIEnv* env, jobject, jlong userData, jstring error) 110 | { 111 | dmFacebook::FacebookCommand cmd; 112 | cmd.m_Type = dmFacebook::COMMAND_TYPE_REQUEST_PUBLISH; 113 | cmd.m_Callback = (dmScript::LuaCallbackInfo*)userData; 114 | cmd.m_Error = StrDup(env, error); 115 | dmFacebook::QueuePush(&g_Facebook.m_CommandQueue, &cmd); 116 | } 117 | 118 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onDialogComplete 119 | (JNIEnv *env, jobject, jlong userData, jstring results, jstring error) 120 | { 121 | dmFacebook::FacebookCommand cmd; 122 | cmd.m_Type = dmFacebook::COMMAND_TYPE_DIALOG_COMPLETE; 123 | cmd.m_Callback = (dmScript::LuaCallbackInfo*)userData; 124 | cmd.m_Results = StrDup(env, results); 125 | cmd.m_Error = StrDup(env, error); 126 | dmFacebook::QueuePush(&g_Facebook.m_CommandQueue, &cmd); 127 | } 128 | 129 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onIterateMeEntry 130 | (JNIEnv* env, jobject, jlong user_data, jstring key, jstring value) 131 | { 132 | dmScript::LuaCallbackInfo* callback = (dmScript::LuaCallbackInfo*)user_data; 133 | lua_State* L = dmScript::GetCallbackLuaContext(callback); 134 | if (key) { 135 | const char* str_key = env->GetStringUTFChars(key, 0); 136 | lua_pushstring(L, str_key); 137 | env->ReleaseStringUTFChars(key, str_key); 138 | } else { 139 | lua_pushnil(L); 140 | } 141 | 142 | if (value) { 143 | const char* str_value = env->GetStringUTFChars(value, 0); 144 | lua_pushstring(L, str_value); 145 | env->ReleaseStringUTFChars(value, str_value); 146 | } else { 147 | lua_pushnil(L); 148 | } 149 | lua_rawset(L, -3); 150 | } 151 | 152 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onIteratePermissionsEntry 153 | (JNIEnv* env, jobject, jlong user_data, jstring permission) 154 | { 155 | lua_State* L = (lua_State*)user_data; 156 | int i = lua_objlen(L, -1); 157 | 158 | lua_pushnumber(L, i + 1); 159 | 160 | if (permission) { 161 | const char* str_permission = env->GetStringUTFChars(permission, 0); 162 | lua_pushstring(L, str_permission); 163 | env->ReleaseStringUTFChars(permission, str_permission); 164 | } else { 165 | lua_pushnil(L); 166 | } 167 | lua_rawset(L, -3); 168 | } 169 | 170 | JNIEXPORT void JNICALL Java_com_defold_facebook_FacebookJNI_onFetchDeferredAppLinkData 171 | (JNIEnv* env, jobject, jlong user_data, jstring results, jboolean isError) 172 | { 173 | dmFacebook::FacebookCommand cmd; 174 | cmd.m_Callback = (dmScript::LuaCallbackInfo*)user_data; 175 | cmd.m_Type = dmFacebook::COMMAND_TYPE_DEFERRED_APP_LINK; 176 | if (JNI_TRUE == isError) { 177 | cmd.m_Error = StrDup(env, results); 178 | } else { 179 | cmd.m_Results = StrDup(env, results); 180 | } 181 | dmFacebook::QueuePush(&g_Facebook.m_CommandQueue, &cmd); 182 | } 183 | 184 | #ifdef __cplusplus 185 | } 186 | #endif 187 | 188 | int Platform_FacebookLogout(lua_State* L) 189 | { 190 | int top = lua_gettop(L); 191 | 192 | dmAndroid::ThreadAttacher threadAttacher; 193 | JNIEnv* env = threadAttacher.GetEnv(); 194 | 195 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_Logout); 196 | 197 | assert(top == lua_gettop(L)); 198 | return 0; 199 | } 200 | 201 | int Platform_FacebookLoginWithPermissions(lua_State* L, const char** permissions, 202 | uint32_t permission_count, int audience, dmScript::LuaCallbackInfo* callback) 203 | { 204 | // This function must always return so memory for `permissions` can be free'd. 205 | char cstr_permissions[2048]; 206 | cstr_permissions[0] = 0x0; 207 | dmFacebook::JoinCStringArray(permissions, permission_count, cstr_permissions, sizeof(cstr_permissions) / sizeof(cstr_permissions[0]), ","); 208 | 209 | dmAndroid::ThreadAttacher threadAttacher; 210 | JNIEnv* env = threadAttacher.GetEnv(); 211 | 212 | jstring jstr_permissions = env->NewStringUTF(cstr_permissions); 213 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_LoginWithPermissions, (jlong) callback, (jint) audience, jstr_permissions); 214 | env->DeleteLocalRef(jstr_permissions); 215 | 216 | return 0; 217 | } 218 | 219 | int Platform_FacebookAccessToken(lua_State* L) 220 | { 221 | int top = lua_gettop(L); 222 | 223 | dmAndroid::ThreadAttacher threadAttacher; 224 | JNIEnv* env = threadAttacher.GetEnv(); 225 | 226 | jstring str_access_token = (jstring)env->CallObjectMethod(g_Facebook.m_FB, g_Facebook.m_GetAccessToken); 227 | 228 | if (str_access_token) { 229 | const char* access_token = env->GetStringUTFChars(str_access_token, 0); 230 | lua_pushstring(L, access_token); 231 | env->ReleaseStringUTFChars(str_access_token, access_token); 232 | } else { 233 | lua_pushnil(L); 234 | } 235 | 236 | assert(top + 1 == lua_gettop(L)); 237 | return 1; 238 | } 239 | 240 | int Platform_FacebookPermissions(lua_State* L) 241 | { 242 | int top = lua_gettop(L); 243 | 244 | lua_newtable(L); 245 | 246 | dmAndroid::ThreadAttacher threadAttacher; 247 | JNIEnv* env = threadAttacher.GetEnv(); 248 | 249 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_IteratePermissions, (jlong)L); 250 | 251 | assert(top + 1 == lua_gettop(L)); 252 | return 1; 253 | } 254 | 255 | int Platform_FacebookPostEvent(lua_State* L) 256 | { 257 | int argc = lua_gettop(L); 258 | const char* event = dmFacebook::Analytics::GetEvent(L, 1); 259 | double valueToSum = luaL_checknumber(L, 2); 260 | 261 | // Transform LUA table to a format that can be used by all platforms. 262 | const char* keys[dmFacebook::Analytics::MAX_PARAMS] = { 0 }; 263 | const char* values[dmFacebook::Analytics::MAX_PARAMS] = { 0 }; 264 | unsigned int length = 0; 265 | // TABLE is an optional argument and should only be parsed if provided. 266 | if (argc == 3) 267 | { 268 | length = dmFacebook::Analytics::MAX_PARAMS; 269 | dmFacebook::Analytics::GetParameterTable(L, 3, keys, values, &length); 270 | } 271 | 272 | // Prepare Java call 273 | dmAndroid::ThreadAttacher threadAttacher; 274 | JNIEnv* env = threadAttacher.GetEnv(); 275 | 276 | jstring jEvent = env->NewStringUTF(event); 277 | jdouble jValueToSum = (jdouble) valueToSum; 278 | jclass jStringClass = env->FindClass("java/lang/String"); 279 | jobjectArray jKeys = env->NewObjectArray(length, jStringClass, 0); 280 | jobjectArray jValues = env->NewObjectArray(length, jStringClass, 0); 281 | for (unsigned int i = 0; i < length; ++i) 282 | { 283 | jstring jKey = env->NewStringUTF(keys[i]); 284 | env->SetObjectArrayElement(jKeys, i, jKey); 285 | env->DeleteLocalRef(jKey); 286 | jstring jValue = env->NewStringUTF(values[i]); 287 | env->SetObjectArrayElement(jValues, i, jValue); 288 | env->DeleteLocalRef(jValue); 289 | } 290 | 291 | // Call com.defold.facebook.FacebookJNI.postEvent 292 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_PostEvent, jEvent, jValueToSum, jKeys, jValues); 293 | env->DeleteLocalRef(jKeys); 294 | env->DeleteLocalRef(jValues); 295 | env->DeleteLocalRef(jStringClass); 296 | 297 | return 0; 298 | } 299 | 300 | int Platform_FacebookEnableEventUsage(lua_State* L) 301 | { 302 | dmAndroid::ThreadAttacher threadAttacher; 303 | JNIEnv* env = threadAttacher.GetEnv(); 304 | 305 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_EnableEventUsage); 306 | 307 | return 0; 308 | } 309 | 310 | int Platform_FacebookDisableEventUsage(lua_State* L) 311 | { 312 | dmAndroid::ThreadAttacher threadAttacher; 313 | JNIEnv* env = threadAttacher.GetEnv(); 314 | 315 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_DisableEventUsage); 316 | 317 | return 0; 318 | } 319 | 320 | int Platform_FacebookShowDialog(lua_State* L) 321 | { 322 | int top = lua_gettop(L); 323 | 324 | const char* dialog = luaL_checkstring(L, 1); 325 | luaL_checktype(L, 2, LUA_TTABLE); 326 | 327 | dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 3); 328 | 329 | dmAndroid::ThreadAttacher threadAttacher; 330 | JNIEnv* env = threadAttacher.GetEnv(); 331 | 332 | lua_newtable(L); 333 | int to_index = lua_gettop(L); 334 | if (0 == dmFacebook::DialogTableToAndroid(L, dialog, 2, to_index)) { 335 | lua_pop(L, 1); 336 | assert(top == lua_gettop(L)); 337 | return luaL_error(L, "Could not convert show dialog param table."); 338 | } 339 | 340 | int size_needed = 1 + dmFacebook::LuaTableToJson(L, to_index, 0, 0); 341 | char* params_json = (char*)malloc(size_needed); 342 | 343 | if (params_json == 0 || 0 == dmFacebook::LuaTableToJson(L, to_index, params_json, size_needed)) { 344 | lua_pop(L, 1); 345 | assert(top == lua_gettop(L)); 346 | if( params_json ) { 347 | free(params_json); 348 | } 349 | return luaL_error(L, "Dialog params table too large."); 350 | } 351 | lua_pop(L, 1); 352 | 353 | jstring str_dialog = env->NewStringUTF(dialog); 354 | jstring str_params = env->NewStringUTF(params_json); 355 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_ShowDialog, (jlong)callback, str_dialog, str_params); 356 | env->DeleteLocalRef(str_dialog); 357 | env->DeleteLocalRef(str_params); 358 | free((void*)params_json); 359 | 360 | assert(top == lua_gettop(L)); 361 | return 0; 362 | } 363 | 364 | int Platform_FetchDeferredAppLinkData(lua_State* L, dmScript::LuaCallbackInfo* callback) 365 | { 366 | DM_LUA_STACK_CHECK(L, 0); 367 | 368 | dmAndroid::ThreadAttacher threadAttacher; 369 | JNIEnv* env = threadAttacher.GetEnv(); 370 | 371 | env->CallVoidMethod(g_Facebook.m_FB, g_Facebook.m_FetchDeferredAppLink, (jlong)callback); 372 | 373 | return 0; 374 | } 375 | 376 | const char* Platform_GetVersion() 377 | { 378 | dmAndroid::ThreadAttacher threadAttacher; 379 | JNIEnv* env = threadAttacher.GetEnv(); 380 | 381 | jstring jSdkVersion = (jstring)env->CallObjectMethod(g_Facebook.m_FB, g_Facebook.m_GetSdkVersion); 382 | const char* out = StrDup(env, jSdkVersion); 383 | 384 | return out; 385 | } 386 | 387 | 388 | bool Platform_FacebookInitialized() 389 | { 390 | return g_Facebook.m_FBApp != 0 && g_Facebook.m_FB != 0; 391 | } 392 | 393 | int Platform_FacebookInit(lua_State* L) 394 | { 395 | dmAndroid::ThreadAttacher threadAttacher; 396 | JNIEnv* env = threadAttacher.GetEnv(); 397 | 398 | // FacebookAppJNI 399 | { 400 | jclass fb_app_class = dmAndroid::LoadClass(env, "com.defold.facebook.FacebookAppJNI"); 401 | 402 | g_Facebook.m_Activate = env->GetMethodID(fb_app_class, "activate", "()V"); 403 | g_Facebook.m_Deactivate = env->GetMethodID(fb_app_class, "deactivate", "()V"); 404 | 405 | jmethodID fb_app_jni_constructor = env->GetMethodID(fb_app_class, "", "(Landroid/app/Activity;Ljava/lang/String;)V"); 406 | jstring str_app_id = env->NewStringUTF(g_Facebook.m_AppId); 407 | g_Facebook.m_FBApp = env->NewGlobalRef(env->NewObject(fb_app_class, fb_app_jni_constructor, g_AndroidApp->activity->clazz, str_app_id)); 408 | env->DeleteLocalRef(str_app_id); 409 | 410 | if(!g_Facebook.m_DisableFaceBookEvents) 411 | { 412 | env->CallVoidMethod(g_Facebook.m_FBApp, g_Facebook.m_Activate); 413 | } 414 | } 415 | 416 | // FacebookJNI 417 | { 418 | jclass fb_class = dmAndroid::LoadClass(env, "com.defold.facebook.FacebookJNI"); 419 | 420 | g_Facebook.m_GetSdkVersion = env->GetMethodID(fb_class, "getSdkVersion", "()Ljava/lang/String;"); 421 | g_Facebook.m_Logout = env->GetMethodID(fb_class, "logout", "()V"); 422 | g_Facebook.m_IteratePermissions = env->GetMethodID(fb_class, "iteratePermissions", "(J)V"); 423 | g_Facebook.m_GetAccessToken = env->GetMethodID(fb_class, "getAccessToken", "()Ljava/lang/String;"); 424 | g_Facebook.m_ShowDialog = env->GetMethodID(fb_class, "showDialog", "(JLjava/lang/String;Ljava/lang/String;)V"); 425 | g_Facebook.m_FetchDeferredAppLink = env->GetMethodID(fb_class, "fetchDeferredAppLinkData", "(J)V"); 426 | 427 | g_Facebook.m_LoginWithPermissions = env->GetMethodID(fb_class, "loginWithPermissions", "(JILjava/lang/String;)V"); 428 | 429 | g_Facebook.m_PostEvent = env->GetMethodID(fb_class, "postEvent", "(Ljava/lang/String;D[Ljava/lang/String;[Ljava/lang/String;)V"); 430 | g_Facebook.m_EnableEventUsage = env->GetMethodID(fb_class, "enableEventUsage", "()V"); 431 | g_Facebook.m_DisableEventUsage = env->GetMethodID(fb_class, "disableEventUsage", "()V"); 432 | 433 | jmethodID fb_jni_constructor = env->GetMethodID(fb_class, "", "(Landroid/app/Activity;Ljava/lang/String;)V"); 434 | 435 | jstring str_app_id = env->NewStringUTF(g_Facebook.m_AppId); 436 | g_Facebook.m_FB = env->NewGlobalRef(env->NewObject(fb_class, fb_jni_constructor, g_AndroidApp->activity->clazz, str_app_id)); 437 | env->DeleteLocalRef(str_app_id); 438 | } 439 | 440 | return 0; 441 | } 442 | 443 | dmExtension::Result Platform_AppInitializeFacebook(dmExtension::AppParams* params, const char* app_id) 444 | { 445 | g_Facebook.m_AppId = app_id; 446 | g_Facebook.m_DisableFaceBookEvents = dmConfigFile::GetInt(params->m_ConfigFile, "facebook.disable_events", 0); 447 | dmFacebook::QueueCreate(&g_Facebook.m_CommandQueue); 448 | 449 | return dmExtension::RESULT_OK; 450 | } 451 | 452 | dmExtension::Result Platform_AppFinalizeFacebook(dmExtension::AppParams* params) 453 | { 454 | if (g_Facebook.m_FBApp != 0) 455 | { 456 | dmAndroid::ThreadAttacher threadAttacher; 457 | JNIEnv* env = threadAttacher.GetEnv(); 458 | 459 | if(!g_Facebook.m_DisableFaceBookEvents) 460 | { 461 | env->CallVoidMethod(g_Facebook.m_FBApp, g_Facebook.m_Deactivate); 462 | } 463 | env->DeleteGlobalRef(g_Facebook.m_FBApp); 464 | 465 | g_Facebook.m_FBApp = 0; 466 | memset(&g_Facebook, 0x00, sizeof(Facebook)); 467 | } 468 | 469 | return dmExtension::RESULT_OK; 470 | } 471 | 472 | dmExtension::Result Platform_InitializeFacebook(dmExtension::Params* params) 473 | { 474 | g_Facebook.m_RefCount++; 475 | 476 | return dmExtension::RESULT_OK; 477 | } 478 | 479 | dmExtension::Result Platform_UpdateFacebook(dmExtension::Params* params) 480 | { 481 | dmFacebook::QueueFlush(&g_Facebook.m_CommandQueue, dmFacebook::HandleCommand, (void*)params->m_L); 482 | return dmExtension::RESULT_OK; 483 | } 484 | 485 | dmExtension::Result Platform_FinalizeFacebook(dmExtension::Params* params) 486 | { 487 | if (g_Facebook.m_FB != NULL) 488 | { 489 | if (--g_Facebook.m_RefCount == 0) 490 | { 491 | dmAndroid::ThreadAttacher threadAttacher; 492 | JNIEnv* env = threadAttacher.GetEnv(); 493 | 494 | env->DeleteGlobalRef(g_Facebook.m_FB); 495 | 496 | g_Facebook.m_FB = NULL; 497 | 498 | dmFacebook::QueueDestroy(&g_Facebook.m_CommandQueue); 499 | } 500 | } 501 | 502 | return dmExtension::RESULT_OK; 503 | } 504 | 505 | void Platform_OnEventFacebook(dmExtension::Params* params, const dmExtension::Event* event) 506 | { 507 | if( (g_Facebook.m_FBApp) && (!g_Facebook.m_DisableFaceBookEvents ) ) 508 | { 509 | dmAndroid::ThreadAttacher threadAttacher; 510 | JNIEnv* env = threadAttacher.GetEnv(); 511 | 512 | if( event->m_Event == dmExtension::EVENT_ID_ACTIVATEAPP ) 513 | env->CallVoidMethod(g_Facebook.m_FBApp, g_Facebook.m_Activate); 514 | else if( event->m_Event == dmExtension::EVENT_ID_DEACTIVATEAPP ) 515 | env->CallVoidMethod(g_Facebook.m_FBApp, g_Facebook.m_Deactivate); 516 | } 517 | } 518 | 519 | #endif // DM_PLATFORM_ANDROID 520 | -------------------------------------------------------------------------------- /facebook/src/facebook_emscripten.cpp: -------------------------------------------------------------------------------- 1 | #if defined(DM_PLATFORM_HTML5) 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "facebook_private.h" 11 | #include "facebook_util.h" 12 | #include "facebook_analytics.h" 13 | 14 | // Must match iOS for now 15 | enum Audience 16 | { 17 | AUDIENCE_NONE = 0, 18 | AUDIENCE_ONLYME = 10, 19 | AUDIENCE_FRIENDS = 20, 20 | AUDIENCE_EVERYONE = 30 21 | }; 22 | 23 | struct Facebook 24 | { 25 | Facebook() 26 | { 27 | memset(this, 0, sizeof(*this)); 28 | m_Initialized = false; 29 | } 30 | dmScript::LuaCallbackInfo* m_Callback; 31 | const char* m_appId; 32 | const char* m_PermissionsJson; 33 | bool m_Initialized; 34 | }; 35 | 36 | Facebook g_Facebook; 37 | 38 | typedef void (*OnAccessTokenCallback)(void* L, const char* access_token); 39 | typedef void (*OnShowDialogCallback)(void* luacallback, const char* url, const char* error); 40 | typedef void (*OnLoginWithPermissionsCallback)(void* luacallback, int status, const char* error, const char* permissions_json); 41 | 42 | extern "C" { 43 | // Implementation in library_facebook.js 44 | void dmFacebookInitialize(const char* app_id, const char* version); 45 | void dmFacebookAccessToken(OnAccessTokenCallback callback, lua_State* L); 46 | void dmFacebookShowDialog(const char* params, const char* method, OnShowDialogCallback callback, dmScript::LuaCallbackInfo* luacallback); 47 | void dmFacebookDoLogout(); 48 | void dmFacebookLoginWithPermissions(int state_open, int state_closed, int state_failed, const char* permissions, OnLoginWithPermissionsCallback callback, dmScript::LuaCallbackInfo* luacallback); 49 | void dmFacebookPostEvent(const char* event, double valueToSum, const char* keys, const char* values); 50 | void dmFacebookEnableEventUsage(); 51 | void dmFacebookDisableEventUsage(); 52 | } 53 | 54 | int Platform_FacebookLogout(lua_State* L) 55 | { 56 | int top = lua_gettop(L); 57 | 58 | dmFacebookDoLogout(); 59 | 60 | if (g_Facebook.m_PermissionsJson) { 61 | g_Facebook.m_PermissionsJson = 0; 62 | } 63 | 64 | assert(top == lua_gettop(L)); 65 | return 0; 66 | } 67 | 68 | static void OnLoginWithPermissions(dmScript::LuaCallbackInfo* callback, int status, const char* error, const char* permissions_json) 69 | { 70 | if (permissions_json != 0x0) 71 | { 72 | g_Facebook.m_PermissionsJson = permissions_json; 73 | } 74 | 75 | dmFacebook::RunStatusCallback(callback, error, status); 76 | dmScript::DestroyCallback(callback); 77 | } 78 | 79 | int Platform_FacebookLoginWithPermissions(lua_State* L, const char** permissions, 80 | uint32_t permission_count, int audience, dmScript::LuaCallbackInfo* callback) 81 | { 82 | char cstr_permissions[2048]; 83 | cstr_permissions[0] = 0x0; 84 | dmFacebook::JoinCStringArray(permissions, permission_count, cstr_permissions, 85 | sizeof(cstr_permissions) / sizeof(cstr_permissions[0]), ","); 86 | 87 | dmFacebookLoginWithPermissions( 88 | dmFacebook::STATE_OPEN, dmFacebook::STATE_CLOSED, dmFacebook::STATE_CLOSED_LOGIN_FAILED, 89 | cstr_permissions, (OnLoginWithPermissionsCallback) OnLoginWithPermissions, callback); 90 | return 0; 91 | } 92 | 93 | static void OnAccessTokenComplete(void* L, const char* access_token) 94 | { 95 | if(access_token != 0) 96 | { 97 | lua_pushstring((lua_State *)L, access_token); 98 | } 99 | else 100 | { 101 | lua_pushnil((lua_State *)L); 102 | dmLogError("Access_token is null (logged out?)."); 103 | } 104 | } 105 | 106 | int Platform_FacebookAccessToken(lua_State* L) 107 | { 108 | DM_LUA_STACK_CHECK(L, 1); 109 | 110 | dmFacebookAccessToken( (OnAccessTokenCallback) OnAccessTokenComplete, L); 111 | 112 | return 1; 113 | } 114 | 115 | int Platform_FacebookPermissions(lua_State* L) 116 | { 117 | DM_LUA_STACK_CHECK(L, 1); 118 | 119 | if(g_Facebook.m_PermissionsJson != 0) 120 | { 121 | // Note that the permissionsJsonArray contains a regular string array in json format, 122 | // e.g. ["foo", "bar", "baz", ...] 123 | if (dmFacebook::PushLuaTableFromJson(L, g_Facebook.m_PermissionsJson)) { 124 | dmLogError("Failed to parse Facebook_Permissions response"); 125 | lua_newtable(L); 126 | } 127 | } 128 | else 129 | { 130 | dmLogError("Got empty Facebook_Permissions response (or FB error)."); 131 | // This follows the iOS implementation... 132 | lua_newtable((lua_State *)L); 133 | } 134 | 135 | return 1; 136 | } 137 | 138 | static void OnShowDialogComplete(dmScript::LuaCallbackInfo* callback, const char* result_json, const char* error) 139 | { 140 | dmFacebook::RunJsonResultCallback(callback, result_json, error); 141 | dmScript::DestroyCallback(callback); 142 | } 143 | 144 | int Platform_FacebookShowDialog(lua_State* L) 145 | { 146 | DM_LUA_STACK_CHECK(L, 0); 147 | 148 | const char* dialog = luaL_checkstring(L, 1); 149 | luaL_checktype(L, 2, LUA_TTABLE); 150 | 151 | dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 3); 152 | 153 | lua_newtable(L); 154 | int to_index = lua_gettop(L); 155 | if (0 == dmFacebook::DialogTableToEmscripten(L, dialog, 2, to_index)) { 156 | lua_pop(L, 1); 157 | return DM_LUA_ERROR("Could not convert show dialog param table."); 158 | } 159 | 160 | int size_needed = 1 + dmFacebook::LuaTableToJson(L, to_index, 0, 0); 161 | char* params_json = (char*)malloc(size_needed); 162 | 163 | if (params_json == 0 || 0 == dmFacebook::LuaTableToJson(L, to_index, params_json, size_needed)) { 164 | lua_pop(L, 1); 165 | if( params_json ) { 166 | free(params_json); 167 | } 168 | return DM_LUA_ERROR("Dialog params table too large."); 169 | } 170 | lua_pop(L, 1); 171 | 172 | dmFacebookShowDialog(params_json, dialog, (OnShowDialogCallback) OnShowDialogComplete, callback); 173 | free(params_json); 174 | return 0; 175 | } 176 | 177 | int Platform_FacebookPostEvent(lua_State* L) 178 | { 179 | DM_LUA_STACK_CHECK(L, 0); 180 | int argc = lua_gettop(L); 181 | const char* event = dmFacebook::Analytics::GetEvent(L, 1); 182 | double valueToSum = luaL_checknumber(L, 2); 183 | 184 | // Transform LUA table to a format that can be used by all platforms. 185 | const char* keys[dmFacebook::Analytics::MAX_PARAMS] = { 0 }; 186 | const char* values[dmFacebook::Analytics::MAX_PARAMS] = { 0 }; 187 | unsigned int length = 0; 188 | // TABLE is an optional argument and should only be parsed if provided. 189 | if (argc == 3) 190 | { 191 | length = dmFacebook::Analytics::MAX_PARAMS; 192 | dmFacebook::Analytics::GetParameterTable(L, 3, keys, values, &length); 193 | } 194 | 195 | const char* json_keys = dmFacebook::CStringArrayToJsonString(keys, length); 196 | const char* json_values = dmFacebook::CStringArrayToJsonString(values, length); 197 | 198 | // Forward call to JavaScript 199 | dmFacebookPostEvent(event, valueToSum, json_keys, json_values); 200 | 201 | free((void*) json_keys); 202 | free((void*) json_values); 203 | 204 | return 0; 205 | } 206 | 207 | int Platform_FacebookEnableEventUsage(lua_State* L) 208 | { 209 | dmFacebookEnableEventUsage(); 210 | 211 | return 0; 212 | } 213 | 214 | int Platform_FacebookDisableEventUsage(lua_State* L) 215 | { 216 | dmFacebookDisableEventUsage(); 217 | 218 | return 0; 219 | } 220 | 221 | int Platform_FetchDeferredAppLinkData(lua_State* L, dmScript::LuaCallbackInfo* callback) 222 | { 223 | dmLogOnceDebug("get_deferred_app_link() function isn't supported on HTML5 platform"); 224 | return 0; 225 | } 226 | 227 | const char* Platform_GetVersion() 228 | { 229 | return strdup(dmFacebook::GRAPH_API_VERSION); 230 | } 231 | 232 | bool Platform_FacebookInitialized() 233 | { 234 | return g_Facebook.m_Initialized; 235 | } 236 | 237 | int Platform_FacebookInit(lua_State* L) 238 | { 239 | DM_LUA_STACK_CHECK(L, 0); 240 | 241 | dmFacebookInitialize(g_Facebook.m_appId, dmFacebook::GRAPH_API_VERSION); 242 | dmLogDebug("FB initialized."); 243 | g_Facebook.m_Initialized = true; 244 | 245 | return 0; 246 | } 247 | 248 | dmExtension::Result Platform_AppInitializeFacebook(dmExtension::AppParams* params, const char* app_id) 249 | { 250 | (void)params; 251 | g_Facebook.m_appId = app_id; 252 | return dmExtension::RESULT_OK; 253 | } 254 | 255 | dmExtension::Result Platform_AppFinalizeFacebook(dmExtension::AppParams* params) 256 | { 257 | (void)params; 258 | return dmExtension::RESULT_OK; 259 | } 260 | 261 | dmExtension::Result Platform_InitializeFacebook(dmExtension::Params* params) 262 | { 263 | 264 | return dmExtension::RESULT_OK; 265 | } 266 | 267 | dmExtension::Result Platform_FinalizeFacebook(dmExtension::Params* params) 268 | { 269 | (void)params; 270 | // TODO: "Uninit" FB SDK here? 271 | g_Facebook = Facebook(); 272 | return dmExtension::RESULT_OK; 273 | } 274 | 275 | dmExtension::Result Platform_UpdateFacebook(dmExtension::Params* params) 276 | { 277 | (void)params; 278 | return dmExtension::RESULT_OK; 279 | } 280 | 281 | void Platform_OnEventFacebook(dmExtension::Params* params, const dmExtension::Event* event) 282 | { 283 | (void)params; (void)event; 284 | } 285 | 286 | #endif // DM_PLATFORM_HTML5 287 | -------------------------------------------------------------------------------- /facebook/src/facebook_private.cpp: -------------------------------------------------------------------------------- 1 | #include "facebook_private.h" 2 | #include "facebook_util.h" 3 | 4 | #include 5 | 6 | namespace dmFacebook 7 | { 8 | 9 | void QueueCreate(CommandQueue* queue) 10 | { 11 | queue->m_Mutex = dmMutex::New(); 12 | } 13 | 14 | void QueueDestroy(CommandQueue* queue) 15 | { 16 | dmMutex::Delete(queue->m_Mutex); 17 | } 18 | 19 | void QueuePush(CommandQueue* queue, FacebookCommand* cmd) 20 | { 21 | DM_MUTEX_SCOPED_LOCK(queue->m_Mutex); 22 | if (queue->m_Commands.Full()) 23 | { 24 | queue->m_Commands.OffsetCapacity(8); 25 | } 26 | queue->m_Commands.Push(*cmd); 27 | } 28 | 29 | void QueueFlush(CommandQueue* queue, FacebookCommandFn fn, void* ctx) 30 | { 31 | if (queue->m_Commands.Empty()) 32 | { 33 | return; 34 | } 35 | 36 | dmArray tmp; 37 | { 38 | DM_MUTEX_SCOPED_LOCK(queue->m_Mutex); 39 | tmp.Swap(queue->m_Commands); 40 | } 41 | tmp.Map(fn, ctx); 42 | } 43 | 44 | void HandleCommand(dmFacebook::FacebookCommand* cmd, void* ctx) 45 | { 46 | lua_State* paramsL = (lua_State*)ctx; 47 | 48 | // Checking that we're in the correct context (in case of a non shared Lua state) 49 | lua_State* L = dmScript::GetCallbackLuaContext(cmd->m_Callback); 50 | if (L != paramsL) 51 | return; 52 | 53 | switch (cmd->m_Type) 54 | { 55 | case dmFacebook::COMMAND_TYPE_LOGIN: 56 | dmFacebook::RunStateCallback(cmd->m_Callback, cmd->m_State, cmd->m_Error); // DEPRECATED 57 | break; 58 | case dmFacebook::COMMAND_TYPE_REQUEST_READ: 59 | case dmFacebook::COMMAND_TYPE_REQUEST_PUBLISH: 60 | case dmFacebook::COMMAND_TYPE_LOGIN_WITH_PERMISSIONS: 61 | dmFacebook::RunStatusCallback(cmd->m_Callback, cmd->m_Error, cmd->m_State); 62 | break; 63 | case dmFacebook::COMMAND_TYPE_DIALOG_COMPLETE: 64 | case dmFacebook::COMMAND_TYPE_DEFERRED_APP_LINK: 65 | dmFacebook::RunJsonResultCallback(cmd->m_Callback, cmd->m_Results, cmd->m_Error); 66 | break; 67 | } 68 | free((void*)cmd->m_Results); 69 | free((void*)cmd->m_Error); 70 | dmScript::DestroyCallback(cmd->m_Callback); 71 | } 72 | 73 | } // namespace 74 | -------------------------------------------------------------------------------- /facebook/src/facebook_private.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "facebook_util.h" 4 | 5 | #include 6 | #include 7 | #include 8 | struct lua_State; 9 | 10 | namespace dmFacebook { 11 | 12 | const char* const GRAPH_API_VERSION = "v3.3"; 13 | 14 | enum State { 15 | STATE_CREATED = 1, 16 | STATE_CREATED_TOKEN_LOADED = 2, 17 | STATE_CREATED_OPENING = 3, 18 | STATE_OPEN = 4, 19 | STATE_OPEN_TOKEN_EXTENDED = 5, 20 | STATE_CLOSED_LOGIN_FAILED = 6, 21 | STATE_CLOSED = 7, 22 | }; 23 | 24 | enum GameRequestAction { 25 | GAMEREQUEST_ACTIONTYPE_NONE = 1, 26 | GAMEREQUEST_ACTIONTYPE_SEND = 2, 27 | GAMEREQUEST_ACTIONTYPE_ASKFOR = 3, 28 | GAMEREQUEST_ACTIONTYPE_TURN = 4, 29 | }; 30 | 31 | enum GameRequestFilters { 32 | GAMEREQUEST_FILTER_NONE = 1, 33 | GAMEREQUEST_FILTER_APPUSERS = 2, 34 | GAMEREQUEST_FILTER_APPNONUSERS = 3, 35 | }; 36 | 37 | enum Audience { 38 | AUDIENCE_NONE = 1, 39 | AUDIENCE_ONLYME = 2, 40 | AUDIENCE_FRIENDS = 3, 41 | AUDIENCE_EVERYONE = 4, 42 | }; 43 | 44 | enum CommandType { 45 | COMMAND_TYPE_LOGIN = 1, 46 | COMMAND_TYPE_REQUEST_READ = 2, 47 | COMMAND_TYPE_REQUEST_PUBLISH = 3, 48 | COMMAND_TYPE_DIALOG_COMPLETE = 4, 49 | COMMAND_TYPE_LOGIN_WITH_PERMISSIONS = 5, 50 | COMMAND_TYPE_DEFERRED_APP_LINK = 6 51 | }; 52 | 53 | enum LoginTracking { 54 | LOGIN_TRACKING_LIMITED = 1, 55 | LOGIN_TRACKING_ENABLED = 2 56 | }; 57 | 58 | struct FacebookCommand 59 | { 60 | FacebookCommand() 61 | { 62 | memset(this, 0, sizeof(FacebookCommand)); 63 | } 64 | 65 | dmScript::LuaCallbackInfo* m_Callback; 66 | const char* m_Results; 67 | const char* m_Error; 68 | uint8_t m_Type; 69 | uint16_t m_State; 70 | }; 71 | 72 | struct CommandQueue 73 | { 74 | dmArray m_Commands; 75 | dmMutex::HMutex m_Mutex; 76 | }; 77 | 78 | typedef void (*FacebookCommandFn)(FacebookCommand* cmd, void* ctx); 79 | 80 | void QueueCreate(CommandQueue* queue); 81 | void QueueDestroy(CommandQueue* queue); 82 | void QueuePush(CommandQueue* queue, FacebookCommand* cmd); 83 | void QueueFlush(CommandQueue* queue, FacebookCommandFn fn, void* ctx); 84 | 85 | void HandleCommand(FacebookCommand* cmd, void* ctx); 86 | 87 | /* 88 | Notes on facebook.show_dialog in regards to FB SDK 4 89 | 90 | Dialog parameters have been updated to align with field names in the 91 | current SDK version. Names have been chosen with a preference of JS 92 | field names (object_id vs objectID for example). 93 | 94 | Some fields that have changed names in the SDK still exist, but are 95 | considered deprecated (title -> caption). 96 | 97 | Results are forwarded as-is from each platform. 98 | 99 | facebook.show_dialog( dialog_type, param_table, callback_func ): 100 | 101 | dialog_type == "apprequests": 102 | Details for each parameter: https://developers.facebook.com/docs/games/services/gamerequests/v3.3#dialogparameters 103 | 104 | arg type JS iOS Android 105 | - title : string [ title, title, setTitle ] 106 | - message : string [ message, message, setMessage ] 107 | - action_type : int [ action_type, actionType, setActionType ] 108 | - filters : int [ filters, filters, setFilters ] 109 | - data : string [ data, data, setData ] 110 | - object_id : string [ object_id, objectID, setObjectId ] 111 | - suggestions : array [ suggestions, recipientSuggestions, setSuggestions ] 112 | - to : string [ to, recipients, setTo ] 113 | 114 | dialog_type == "feed": 115 | Details for each parameter: https://developers.facebook.com/docs/sharing/reference/feed-dialog/v3.3#params 116 | 117 | arg type JS iOS Android 118 | - caption : string [ caption, contentTitle, setContentTitle ] 119 | - description : string [ description, contentDescription, setContentDescription ] 120 | - picture : string [ picture, imageURL, setImageUrl ] 121 | - link : string [ link, contentURL, setContentUrl ] 122 | - people_ids : array [ -, peopleIDs, setPeopleIds ] 123 | - place_id : string [ -, placeID, setPlaceId ] 124 | - ref : string [ ref, ref, setRef ] 125 | 126 | Deprecated fields: 127 | - title : string, use caption instead 128 | 129 | dialog_type == "appinvite": (Only available under iOS and Android) 130 | Details for each parameter: https://developers.facebook.com/docs/reference/ios/current/class/FBSDKAppInviteContent/ 131 | 132 | arg type iOS / Android 133 | - url : string [ appLinkURL / setApplinkUrl ] 134 | - preview_image_url : string [ appInvitePreviewImageURL / setPreviewImageUrl ] 135 | 136 | */ 137 | 138 | } 139 | 140 | // Caller must free the returned memory! 141 | const char* Platform_GetVersion(); 142 | 143 | int Platform_FacebookInit(lua_State* L); 144 | int Platform_FacebookLogout(lua_State* L); 145 | int Platform_FacebookAccessToken(lua_State* L); 146 | int Platform_FacebookPermissions(lua_State* L); 147 | int Platform_FacebookPostEvent(lua_State* L); 148 | int Platform_FacebookEnableEventUsage(lua_State* L); 149 | int Platform_FacebookDisableEventUsage(lua_State* L); 150 | int Platform_FacebookEnableAdvertiserTracking(lua_State* L); 151 | int Platform_FacebookDisableAdvertiserTracking(lua_State* L); 152 | int Platform_FacebookShowDialog(lua_State* L); 153 | int Platform_FacebookLoginWithPermissions(lua_State* L, const char** permissions, uint32_t permission_count, int audience, dmScript::LuaCallbackInfo* callback); 154 | int Platform_FetchDeferredAppLinkData(lua_State* L, dmScript::LuaCallbackInfo* callback); 155 | 156 | bool Platform_FacebookInitialized(); 157 | dmExtension::Result Platform_AppInitializeFacebook(dmExtension::AppParams* params, const char* app_id); 158 | dmExtension::Result Platform_AppFinalizeFacebook(dmExtension::AppParams* params); 159 | dmExtension::Result Platform_InitializeFacebook(dmExtension::Params* params); 160 | dmExtension::Result Platform_FinalizeFacebook(dmExtension::Params* params); 161 | dmExtension::Result Platform_UpdateFacebook(dmExtension::Params* params); 162 | void Platform_OnEventFacebook(dmExtension::Params* params, const dmExtension::Event* event); 163 | 164 | void Platform_FacebookSetDefaultAudience(int audience); 165 | void Platform_FacebookLoginWithTrackingPreference(dmFacebook::LoginTracking login_tracking, const char** permissions, uint32_t permission_count, const char* crypto_nonce, dmScript::LuaCallbackInfo* callback); 166 | const char* Platform_FacebookGetCurrentAuthenticationToken(); 167 | int Platform_FacebookGetCurrentProfile(lua_State* L); 168 | -------------------------------------------------------------------------------- /facebook/src/facebook_util.cpp: -------------------------------------------------------------------------------- 1 | #include "facebook_private.h" 2 | #include "facebook_util.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | static int WriteString(char* dst, size_t dst_size, const char* src, size_t src_size) 11 | { 12 | if (!dst) { 13 | return src_size; // If we're only counting the number of characters needed 14 | } 15 | 16 | if (dst_size < src_size) { 17 | return 0; 18 | } 19 | 20 | for (size_t i = 0; i < src_size; ++i) { 21 | dst[i] = src[i]; 22 | } 23 | 24 | return src_size; 25 | } 26 | 27 | void dmFacebook::JoinCStringArray(const char** array, uint32_t arrlen, 28 | char* buffer, uint32_t buflen, const char* delimiter) 29 | { 30 | if (array == 0x0 || arrlen == 0 || buffer == 0x0 || buflen == 0) 31 | { 32 | return; 33 | } 34 | 35 | for (uint32_t i = 0; i < arrlen; ++i) 36 | { 37 | if (i > 0) 38 | { 39 | (void)dmStrlCat(buffer, delimiter, buflen); 40 | } 41 | 42 | (void)dmStrlCat(buffer, array[i], buflen); 43 | } 44 | } 45 | 46 | int dmFacebook::luaTableToCArray(lua_State* L, int index, char** buffer, uint32_t buffer_size) 47 | { 48 | uint32_t entries = 0; 49 | 50 | if (L == 0x0 || buffer == 0x0 || buffer_size == 0) 51 | { 52 | return entries; 53 | } 54 | 55 | lua_pushnil(L); 56 | while (lua_next(L, index)) 57 | { 58 | if (lua_isstring(L, -1)) 59 | { 60 | if (entries < buffer_size) 61 | { 62 | const char* permission = lua_tostring(L, -1); 63 | 64 | uint32_t permission_buffer_len = strlen(permission) + 1; 65 | char* permission_buffer = (char*) malloc(permission_buffer_len); 66 | dmSnPrintf(permission_buffer, permission_buffer_len, "%s", permission); 67 | 68 | buffer[entries++] = permission_buffer; 69 | } 70 | } 71 | else 72 | { 73 | for (uint32_t i = 0; i < entries; ++i) 74 | { 75 | free(buffer[i]); 76 | } 77 | 78 | lua_pop(L, 1); 79 | return -1; 80 | } 81 | 82 | lua_pop(L, 1); 83 | } 84 | 85 | return entries; 86 | } 87 | 88 | int dmFacebook::LuaValueToJsonValue(lua_State* L, int index, char* buffer, size_t buffer_size) 89 | { 90 | // need space for null term 91 | if (buffer && buffer_size < 1) { 92 | return 0; 93 | } 94 | if( buffer ) { 95 | buffer_size--; 96 | } 97 | 98 | int top = lua_gettop(L); 99 | lua_pushvalue(L, index); 100 | index = lua_gettop(L); 101 | int value_type = lua_type(L, index); 102 | size_t len = 0; 103 | 104 | switch (value_type) { 105 | case LUA_TSTRING: { 106 | // String entries need to be escaped and enclosed with citations 107 | const char* v = lua_tolstring(L, index, &len); 108 | len = dmFacebook::WriteEscapedJsonString(buffer, buffer_size, v, len); 109 | break; 110 | } 111 | case LUA_TBOOLEAN: 112 | if (lua_toboolean(L, index)) { 113 | len = WriteString(buffer, buffer_size, "true", 4); 114 | } else { 115 | len = WriteString(buffer, buffer_size, "false", 5); 116 | } 117 | break; 118 | case LUA_TNUMBER: { 119 | const char* v = lua_tolstring(L, index, &len); 120 | len = WriteString(buffer, buffer_size, v, len); 121 | break; 122 | } 123 | case LUA_TTABLE: 124 | len = dmFacebook::LuaTableToJson(L, index, buffer, buffer_size); 125 | break; 126 | case LUA_TNIL: 127 | len = WriteString(buffer, buffer_size, "null", 4); 128 | break; 129 | default: 130 | dmLogError("unserializeable entry: %s (%x)", lua_typename(L, -1), value_type); 131 | break; 132 | } 133 | 134 | if (buffer) { 135 | buffer[len] = '\0'; 136 | } 137 | 138 | lua_pop(L, 1); 139 | assert(top == lua_gettop(L)); 140 | return len; 141 | } 142 | 143 | bool dmFacebook::IsLuaArray(lua_State* L, int index) 144 | { 145 | assert(lua_istable(L, index)); 146 | int top = lua_gettop(L); 147 | 148 | lua_pushnil(L); 149 | int i = 1; 150 | bool table_is_array = true; 151 | while (lua_next(L, index) != 0) { 152 | 153 | // check key type 154 | if (LUA_TNUMBER != lua_type(L, -2) || i != (int)lua_tonumber(L, -2)) { 155 | table_is_array = false; 156 | lua_pop(L, 2); 157 | break; 158 | } 159 | i++; 160 | 161 | lua_pop(L, 1); 162 | } 163 | 164 | assert(top == lua_gettop(L)); 165 | return table_is_array; 166 | } 167 | 168 | int dmFacebook::EscapeJsonString(const char* unescaped, char* escaped, char* end_ptr) 169 | { 170 | if (unescaped == NULL || escaped == NULL) { 171 | return 0; 172 | } 173 | 174 | // keep going through the unescaped until null terminating 175 | // always need at least 3 chars left in escaped buffer, 176 | // 2 for char expanding + 1 for null term 177 | char* esc_start = escaped; 178 | while (*unescaped && escaped <= end_ptr - 3) { 179 | 180 | switch (*unescaped) { 181 | 182 | case '\x22': 183 | *escaped++ = '\\'; 184 | *escaped++ = '"'; 185 | break; 186 | case '\x5C': 187 | *escaped++ = '\\'; 188 | *escaped++ = '\\'; 189 | break; 190 | case '\x08': 191 | *escaped++ = '\\'; 192 | *escaped++ = '\b'; 193 | break; 194 | case '\x0C': 195 | *escaped++ = '\\'; 196 | *escaped++ = '\f'; 197 | break; 198 | case '\x0A': 199 | *escaped++ = '\\'; 200 | *escaped++ = '\n'; 201 | break; 202 | case '\x0D': 203 | *escaped++ = '\\'; 204 | *escaped++ = '\r'; 205 | break; 206 | case '\x09': 207 | *escaped++ = '\\'; 208 | *escaped++ = '\t'; 209 | break; 210 | 211 | default: 212 | *escaped++ = *unescaped; 213 | break; 214 | 215 | } 216 | 217 | unescaped++; 218 | 219 | } 220 | 221 | *escaped = 0; 222 | 223 | return (escaped - esc_start); 224 | } 225 | 226 | int dmFacebook::WriteEscapedJsonString(char* json, size_t json_size, const char* value, size_t value_len) 227 | { 228 | if (value == NULL) { 229 | return 0; 230 | } 231 | 232 | // allocate buffers to hold the escaped key and value strings 233 | char *value_escaped = (char*)malloc((1+value_len*2)*sizeof(char)); 234 | 235 | // escape string characters such as " 236 | value_len = dmFacebook::EscapeJsonString(value, value_escaped, value_escaped+((1+value_len*2)*sizeof(char))); 237 | 238 | int wrote_len = 0; 239 | if (json == 0) { 240 | wrote_len = value_len + 2; 241 | } 242 | else if (value_len+2 <= json_size) { 243 | wrote_len += WriteString(json, json_size, "\"", 1); 244 | wrote_len += WriteString(json+1, json_size-1, (const char*)value_escaped, value_len); 245 | wrote_len += WriteString(json+1+value_len, json_size-1-value_len, "\"", 1); 246 | } 247 | 248 | free(value_escaped); 249 | return wrote_len; 250 | } 251 | 252 | size_t dmFacebook::LuaStringCommaArray(lua_State* L, int index, char* buffer, size_t buffer_size) 253 | { 254 | int top = lua_gettop(L); 255 | lua_pushnil(L); 256 | *buffer = 0; 257 | size_t out_buffer_size = 0; 258 | while (lua_next(L, index) != 0) 259 | { 260 | if (!lua_isstring(L, -1)) 261 | luaL_error(L, "array arguments can only be strings (not %s)", lua_typename(L, lua_type(L, -1))); 262 | if (*buffer != 0) { 263 | dmStrlCat(buffer, ",", buffer_size); 264 | out_buffer_size += 1; 265 | } 266 | size_t lua_str_size; 267 | const char* entry_str = lua_tolstring(L, -1, &lua_str_size); 268 | dmStrlCat(buffer, entry_str, buffer_size); 269 | out_buffer_size += lua_str_size; 270 | lua_pop(L, 1); 271 | } 272 | 273 | assert(top == lua_gettop(L)); 274 | return out_buffer_size; 275 | } 276 | 277 | int dmFacebook::LuaTableToJson(lua_State* L, int index, char* json_buffer, size_t json_buffer_size) 278 | { 279 | assert(lua_istable(L, index)); 280 | int top = lua_gettop(L); 281 | 282 | size_t cursor = 0; 283 | if (json_buffer && json_buffer_size < 1) { 284 | return 0; 285 | } 286 | 287 | bool is_array = dmFacebook::IsLuaArray(L, index); 288 | 289 | if(json_buffer) { 290 | json_buffer[cursor] = is_array ? '[' : '{'; 291 | } 292 | cursor++; 293 | 294 | lua_pushnil(L); 295 | int i = 0; 296 | while (lua_next(L, index) != 0) { 297 | 298 | // add commas 299 | if (i>0) { 300 | if (json_buffer && !WriteString(json_buffer+cursor, json_buffer_size-cursor, ",", 1)) { 301 | lua_pop(L, 2); 302 | assert(top == lua_gettop(L)); 303 | return 0; 304 | } 305 | cursor++; 306 | } 307 | 308 | // write key (skipped if this is an array) 309 | if (!is_array) { 310 | lua_pushvalue(L, -2); 311 | int r = LuaValueToJsonValue(L, lua_gettop(L), json_buffer ? json_buffer+cursor : 0, json_buffer ? json_buffer_size-cursor : 0); 312 | lua_pop(L, 1); 313 | cursor+=r; 314 | 315 | if (json_buffer && !WriteString(json_buffer+cursor, json_buffer_size-cursor, ":", 1)) { 316 | lua_pop(L, 2); 317 | assert(top == lua_gettop(L)); 318 | return 0; 319 | } 320 | cursor++; 321 | } 322 | 323 | // write value 324 | int r = LuaValueToJsonValue(L, lua_gettop(L), json_buffer ? json_buffer+cursor : 0, json_buffer ? json_buffer_size-cursor : 0); 325 | if (!r) { 326 | lua_pop(L, 2); 327 | assert(top == lua_gettop(L)); 328 | return 0; 329 | } 330 | cursor+=r; 331 | 332 | lua_pop(L, 1); 333 | 334 | ++i; 335 | } 336 | 337 | // write ending of json object or array 338 | const char* end = is_array ? (const char*)"]\0" : (const char*)"}\0"; 339 | if (json_buffer && !WriteString(json_buffer+cursor, json_buffer_size-cursor, end, 2)) { 340 | assert(top == lua_gettop(L)); 341 | return 0; 342 | } 343 | cursor+=1; 344 | 345 | assert(top == lua_gettop(L)); 346 | return cursor; 347 | } 348 | 349 | void dmFacebook::SplitStringToTable(lua_State* L, int index, const char* str, char split) 350 | { 351 | int i = 1; 352 | const char* it = str; 353 | while (true) 354 | { 355 | char c = *it; 356 | 357 | if (c == split || c == '\0') 358 | { 359 | lua_pushlstring(L, str, it - str); 360 | lua_rawseti(L, index, i++); 361 | if (c == '\0') { 362 | break; 363 | } 364 | 365 | str = it+1; 366 | } 367 | it++; 368 | } 369 | } 370 | 371 | int dmFacebook::DialogTableToAndroid(lua_State* L, const char* dialog_type, int from_index, int to_index) 372 | { 373 | int top = lua_gettop(L); 374 | int r = DuplicateLuaTable(L, from_index, to_index, 4); 375 | if (!r) { 376 | dmLogError("Could not create Android specific dialog param table."); 377 | assert(top == lua_gettop(L)); 378 | return 0; 379 | } 380 | 381 | if (strcmp(dialog_type, "apprequest") == 0 || strcmp(dialog_type, "apprequests") == 0) 382 | { 383 | // need to convert "to" field into comma separated list if table 384 | lua_getfield(L, to_index, "to"); 385 | if (lua_type(L, lua_gettop(L)) == LUA_TTABLE) { 386 | char t_buf[2048]; 387 | LuaStringCommaArray(L, lua_gettop(L), t_buf, sizeof(t_buf)); 388 | lua_pushstring(L, t_buf); 389 | lua_setfield(L, to_index, "to"); 390 | } 391 | lua_pop(L, 1); 392 | } 393 | 394 | assert(top == lua_gettop(L)); 395 | return 1; 396 | } 397 | 398 | int dmFacebook::DialogTableToEmscripten(lua_State* L, const char* dialog_type, int from_index, int to_index) 399 | { 400 | int top = lua_gettop(L); 401 | int r = DuplicateLuaTable(L, from_index, to_index, 4); 402 | if (!r) { 403 | dmLogError("Could not create Emscripten specific dialog param table."); 404 | assert(top == lua_gettop(L)); 405 | return 0; 406 | } 407 | 408 | if (strcmp(dialog_type, "apprequest") == 0 || strcmp(dialog_type, "apprequests") == 0) 409 | { 410 | // need to convert "to" field into comma separated list if table 411 | lua_getfield(L, to_index, "to"); 412 | if (lua_type(L, lua_gettop(L)) == LUA_TTABLE) { 413 | char t_buf[2048]; 414 | LuaStringCommaArray(L, lua_gettop(L), t_buf, sizeof(t_buf)); 415 | lua_pushstring(L, t_buf); 416 | lua_setfield(L, to_index, "to"); 417 | } 418 | lua_pop(L, 1); 419 | 420 | // if recipients is a table, concat to comma separated list and override "to" field 421 | // (Since FB JS not have "recipient" field, we utilize the "to" field to have same 422 | // functionality and API as other platforms.) 423 | lua_getfield(L, to_index, "recipients"); 424 | if (lua_type(L, lua_gettop(L)) == LUA_TTABLE) { 425 | char t_buf[2048]; 426 | LuaStringCommaArray(L, lua_gettop(L), t_buf, sizeof(t_buf)); 427 | lua_pushstring(L, t_buf); 428 | lua_setfield(L, to_index, "to"); 429 | } 430 | lua_pop(L, 1); 431 | 432 | // need to convert "filters" field from enum to array of string(s) 433 | lua_getfield(L, to_index, "filters"); 434 | if (lua_type(L, lua_gettop(L)) == LUA_TNUMBER) { 435 | 436 | switch (lua_tointeger(L, lua_gettop(L))) { 437 | case GAMEREQUEST_FILTER_APPUSERS: 438 | lua_newtable(L); 439 | lua_pushnumber(L, 1); 440 | lua_pushstring(L, "app_users"); 441 | lua_rawset(L, -3); 442 | lua_setfield(L, to_index, "filters"); 443 | break; 444 | case GAMEREQUEST_FILTER_APPNONUSERS: 445 | lua_newtable(L); 446 | lua_pushnumber(L, 1); 447 | lua_pushstring(L, "app_non_users"); 448 | lua_rawset(L, -3); 449 | lua_setfield(L, to_index, "filters"); 450 | break; 451 | } 452 | } 453 | lua_pop(L, 1); 454 | 455 | // need to convert "action_type" field from enum to string 456 | lua_getfield(L, to_index, "action_type"); 457 | if (lua_type(L, lua_gettop(L)) == LUA_TNUMBER) { 458 | switch (lua_tointeger(L, lua_gettop(L))) { 459 | case GAMEREQUEST_ACTIONTYPE_SEND: 460 | lua_pushstring(L, "send"); 461 | lua_setfield(L, to_index, "action_type"); 462 | break; 463 | case GAMEREQUEST_ACTIONTYPE_ASKFOR: 464 | lua_pushstring(L, "askfor"); 465 | lua_setfield(L, to_index, "action_type"); 466 | break; 467 | case GAMEREQUEST_ACTIONTYPE_TURN: 468 | lua_pushstring(L, "turn"); 469 | lua_setfield(L, to_index, "action_type"); 470 | break; 471 | } 472 | } 473 | lua_pop(L, 1); 474 | } 475 | 476 | assert(top == lua_gettop(L)); 477 | return 1; 478 | } 479 | 480 | // Unused? 481 | int dmFacebook::DuplicateLuaTable(lua_State* L, int from_index, int to_index, unsigned int max_recursion_depth) 482 | { 483 | assert(lua_istable(L, from_index)); 484 | assert(lua_istable(L, to_index)); 485 | if (max_recursion_depth == 0) { 486 | dmLogError("Max recursion depth reached when duplicating Lua table."); 487 | return 0; 488 | } 489 | 490 | int top = lua_gettop(L); 491 | int ret = 1; 492 | 493 | lua_pushnil(L); 494 | while (lua_next(L, from_index) != 0 && ret) { 495 | 496 | // we need to be using absolute stack positions 497 | int t_top = lua_gettop(L); 498 | int key_index = t_top-1; 499 | int value_index = t_top; 500 | 501 | int key_type = lua_type(L, key_index); 502 | int value_type = lua_type(L, value_index); 503 | 504 | // copy key 505 | switch (key_type) { 506 | case LUA_TSTRING: 507 | case LUA_TNUMBER: 508 | lua_pushvalue(L, key_index); 509 | break; 510 | default: 511 | dmLogError("invalid key type: %s (%x)", lua_typename(L, key_type), key_type); 512 | lua_pushnil(L); 513 | ret = 0; 514 | break; 515 | } 516 | 517 | // copy value 518 | switch (value_type) { 519 | case LUA_TSTRING: 520 | case LUA_TNUMBER: 521 | lua_pushvalue(L, value_index); 522 | break; 523 | case LUA_TTABLE: 524 | lua_newtable(L); 525 | ret = DuplicateLuaTable(L, value_index, lua_gettop(L), max_recursion_depth-1); 526 | break; 527 | default: 528 | dmLogError("invalid value type: %s (%x)", lua_typename(L, value_type), value_type); 529 | lua_pushnil(L); 530 | ret = 0; 531 | break; 532 | } 533 | lua_rawset(L, to_index); 534 | 535 | lua_pop(L, 1); 536 | } 537 | 538 | assert(top == lua_gettop(L)); 539 | return ret; 540 | } 541 | 542 | size_t dmFacebook::CountStringArrayLength(lua_State* L, int table_index, size_t& entry_count) 543 | { 544 | int top = lua_gettop(L); 545 | 546 | lua_pushnil(L); 547 | 548 | size_t needed_size = 0; 549 | while (lua_next(L, table_index) != 0) 550 | { 551 | if (!lua_isstring(L, -1)) { 552 | return luaL_error(L, "array arguments can only be strings (not %s)", lua_typename(L, lua_type(L, -1))); 553 | } 554 | 555 | size_t lua_str_size = 0; 556 | lua_tolstring(L, -1, &lua_str_size); 557 | needed_size += lua_str_size; 558 | entry_count++; 559 | lua_pop(L, 1); 560 | } 561 | 562 | assert(top == lua_gettop(L)); 563 | 564 | return needed_size; 565 | } 566 | 567 | int dmFacebook::PushLuaTableFromJson(lua_State* L, const char* json) 568 | { 569 | dmScript::JsonToLua(L, json, strlen(json)); // throws lua error if it fails 570 | return 0; 571 | } 572 | 573 | void dmFacebook::RunStatusCallback(dmScript::LuaCallbackInfo* callback, const char* error, int status) 574 | { 575 | lua_State* L = dmScript::GetCallbackLuaContext(callback); 576 | DM_LUA_STACK_CHECK(L, 0); 577 | 578 | if (!dmScript::SetupCallback(callback)) { 579 | return; 580 | } 581 | 582 | lua_newtable(L); 583 | 584 | if (error != NULL) { 585 | lua_pushstring(L, error); 586 | lua_setfield(L, -2, "error"); 587 | } 588 | 589 | lua_pushnumber(L, status); 590 | lua_setfield(L, -2, "status"); 591 | 592 | int ret = dmScript::PCall(L, 2, 0); 593 | (void)ret; 594 | 595 | dmScript::TeardownCallback(callback); 596 | } 597 | 598 | void dmFacebook::RunJsonResultCallback(dmScript::LuaCallbackInfo* callback, const char* json, const char* error) 599 | { 600 | lua_State* L = dmScript::GetCallbackLuaContext(callback); 601 | DM_LUA_STACK_CHECK(L, 0); 602 | 603 | if (!dmScript::SetupCallback(callback)) { 604 | return; 605 | } 606 | 607 | if (!json) 608 | { 609 | lua_pushnil(L); // if an error, the result table is nil 610 | } 611 | else 612 | { 613 | dmScript::JsonToLua(L, json, strlen(json)); // throws lua error if it fails 614 | } 615 | 616 | dmFacebook::PushError(L, error); 617 | 618 | int ret = dmScript::PCall(L, 3, 0); 619 | (void)ret; 620 | 621 | dmScript::TeardownCallback(callback); 622 | } 623 | 624 | // Deprecated. Used for the old "facebook.login()" flow 625 | void dmFacebook::RunStateCallback(dmScript::LuaCallbackInfo* callback, int state, const char* error) 626 | { 627 | lua_State* L = dmScript::GetCallbackLuaContext(callback); 628 | DM_LUA_STACK_CHECK(L, 0); 629 | 630 | if (!dmScript::SetupCallback(callback)) { 631 | return; 632 | } 633 | 634 | lua_pushnumber(L, (lua_Number) state); 635 | dmFacebook::PushError(L, error); 636 | 637 | int ret = dmScript::PCall(L, 3, 0); 638 | (void)ret; 639 | 640 | dmScript::TeardownCallback(callback); 641 | } 642 | 643 | void dmFacebook::PushError(lua_State*L, const char* error) 644 | { 645 | // Could be extended with error codes etc 646 | if (error != NULL) { 647 | lua_newtable(L); 648 | lua_pushstring(L, error); 649 | lua_setfield(L, -2, "error"); 650 | } else { 651 | lua_pushnil(L); 652 | } 653 | } 654 | 655 | const char* dmFacebook::CStringArrayToJsonString(const char** array, unsigned int length) 656 | { 657 | // Calculate the memory required to store the JSON string. 658 | unsigned int data_length = 2 + length * 2 + (length - 1); 659 | for (unsigned int i = 0; i < length; ++i) 660 | { 661 | data_length += strlen(array[i]); 662 | for (unsigned int n = 0; n < strlen(array[i]); ++n) 663 | { 664 | if (array[i][n] == '"') 665 | { 666 | // We will have to escape this character with a backslash 667 | data_length += 1; 668 | } 669 | } 670 | } 671 | 672 | // Allocate memory for the JSON string, 673 | // this has to be free'd by the caller. 674 | char* json_array = (char*) malloc(data_length + 1); 675 | if (json_array != 0) 676 | { 677 | unsigned int position = 0; 678 | memset((void*) json_array, 0, data_length + 1); 679 | 680 | json_array[position++] = '['; 681 | for (unsigned int i = 0; i < length; ++i) 682 | { 683 | json_array[position++] = '"'; 684 | for (unsigned int n = 0; n < strlen(array[i]); ++n) 685 | { 686 | if (array[i][n] == '"') 687 | { 688 | json_array[position++] = '\\'; 689 | } 690 | json_array[position++] = array[i][n]; 691 | } 692 | json_array[position++] = '"'; 693 | } 694 | 695 | json_array[position++] = ']'; 696 | json_array[position] = '\0'; 697 | } 698 | 699 | return json_array; 700 | } 701 | -------------------------------------------------------------------------------- /facebook/src/facebook_util.h: -------------------------------------------------------------------------------- 1 | #ifndef DM_FACEBOOK_UTIL_H 2 | #define DM_FACEBOOK_UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct lua_State; 9 | 10 | namespace dmFacebook { 11 | 12 | /** 13 | * Check if a Lua table can be considered an array. 14 | * If the supplied table only has number keys in ascending order. 15 | * @param L Lua state 16 | * @param index Stack location of Lua table 17 | * @return 1 if table can be considered an array, 0 otherwise 18 | */ 19 | bool IsLuaArray(lua_State* L, int index); 20 | 21 | /** 22 | * Helper function to escape "escapable character sequences" in a string. 23 | * Used by LuaDialogParamsToJson to escape strings and keys in the JSON table. 24 | * @param unescaped String to escape 25 | * @param escaped A user allocated char buffer where the escaped string will be stored. 26 | * @param end_ptr Pointer to the end of the escaped buffer 27 | * @return Escaped string length 28 | */ 29 | int EscapeJsonString(const char* unescaped, char* escaped, char* end_ptr); 30 | 31 | /** 32 | * Duplicates a Lua table contents into a new table. 33 | * Only the most basic entry types are handled. number, string and tables for values, 34 | * and number and strings for keys. 35 | * @note There is no cyclic reference checking just a max recursion depth. 36 | * @param L Lua state 37 | * @param from_index Stack location of input table 38 | * @param to_index Stack location of output table 39 | * @param max_recursion_depth Max table recursions allowed 40 | * @return 1 on success, 0 on failure 41 | */ 42 | int DuplicateLuaTable(lua_State* L, int from_index, int to_index, unsigned int max_recursion_depth); 43 | 44 | /** 45 | * Converts a Lua table into a comma seperated string. The table will be treated as an 46 | * array, ie. keys will not be handled. 47 | * @param L Lua state 48 | * @param index Stack location of Lua table 49 | * @param buffer Output char buffer 50 | * @param buffer_size Output buffer size 51 | * @return Length of output string 52 | */ 53 | size_t LuaStringCommaArray(lua_State* L, int index, char* buffer, size_t buffer_size); 54 | 55 | /** 56 | * Convert a Lua value into a JSON value, encoded as a non-prettyfied null terminated string. 57 | * @param L Lua state 58 | * @param index Stack location of Lua table 59 | * @param buffer Output JSON char buffer 60 | * @param buffer_size Output buffer size 61 | * @return Length of output string, 0 if conversion failed 62 | */ 63 | int LuaValueToJsonValue(lua_State* L, int index, char* buffer, size_t buffer_size); 64 | 65 | /** 66 | * Convert a Lua table into a JSON object, encoded as a non-prettyfied null terminated string. 67 | * If the destination buffer is null, the function will still return the number of characters needed to store the string (minus the null space character). 68 | * @param L Lua state 69 | * @param index Stack location of Lua table 70 | * @param buffer Output JSON char buffer. May be null 71 | * @param buffer_size Output buffer size 72 | * @return Length of output string, 0 if conversion failed 73 | */ 74 | int LuaTableToJson(lua_State* L, int index, char* buffer, size_t buffer_size); 75 | 76 | /** 77 | * Split a string into a Lua table of strings, splits on the char in 'split'. 78 | * @param L Lua state 79 | * @param index Stack location of Lua table to populate. 80 | * @param str String to split 81 | * @param split Char to split on. 82 | */ 83 | void SplitStringToTable(lua_State* L, int index, const char* str, char split); 84 | 85 | /** 86 | * Escapes "escapable character sequences" in a string and writes it to an output buffer. 87 | * @param json_buffer Output JSON char buffer 88 | * @param json_buffer_size Output buffer size 89 | * @param unescaped_value Unescaped string buffer 90 | * @param unescaped_value_len Length of unescaped string 91 | * @return Length of output string, 0 if write failed 92 | */ 93 | int WriteEscapedJsonString(char* json_buffer, size_t json_buffer_size, const char* unescaped_value, size_t unescaped_value_len); 94 | 95 | /** 96 | * Converts a Lua table to a platform specific "facebook.show_dialog" param table. 97 | * The Lua table will be formatted to be used with the internal functionality of 98 | * the JavaScript Facebook SDK. The formatting tries to unify the field names 99 | * and value types with the iOS implementation. 100 | * @param L Lua state 101 | * @param dialog_type Dialog type that the param table will be used for 102 | * @param from_index Stack location of input table 103 | * @param to_index Stack location of output table 104 | * @return 1 on success, 0 on failure 105 | */ 106 | int DialogTableToEmscripten(lua_State* L, const char* dialog_type, int from_index, int to_index); 107 | 108 | /** 109 | * Converts a Lua table to a platform specific "facebook.show_dialog" param table. 110 | * The Lua table will be formatted to be used with the internal functionality of 111 | * the Android Facebook SDK. The formatting tries to unify the field names 112 | * and value types with the iOS implementation. 113 | * @param L Lua state 114 | * @param dialog_type Dialog type that the param table will be used for 115 | * @param from_index Stack location of input table 116 | * @param to_index Stack location of output table 117 | * @return 1 on success, 0 on failure 118 | */ 119 | int DialogTableToAndroid(lua_State* L, const char* dialog_type, int from_index, int to_index); 120 | 121 | /** 122 | * Count all length of all string values in a Lua table. 123 | * @param L Lua state 124 | * @param table_index Stack location of table 125 | * @param entry_count Count of string entries in table 126 | * @return Total length of all string values 127 | */ 128 | size_t CountStringArrayLength(lua_State* L, int table_index, size_t& entry_count); 129 | 130 | void JoinCStringArray(const char** array, uint32_t arrlen, char* buffer, uint32_t buflen, const char* delimiter); 131 | const char* CStringArrayToJsonString(const char** array, unsigned int length); 132 | 133 | int luaTableToCArray(lua_State* L, int index, char** buffer, uint32_t buffer_size); 134 | 135 | int PushLuaTableFromJson(lua_State* L, const char* json); 136 | 137 | void RunStatusCallback(dmScript::LuaCallbackInfo* callback, const char* error, int status); 138 | 139 | void RunJsonResultCallback(dmScript::LuaCallbackInfo* callback, const char* jsonresult, const char* error); 140 | 141 | void RunStateCallback(dmScript::LuaCallbackInfo* callback, int state, const char* error); 142 | 143 | void PushError(lua_State* L, const char* error); 144 | } 145 | 146 | #endif // #ifndef DM_FACEBOOK_UTIL_H 147 | -------------------------------------------------------------------------------- /facebook/src/java/com/defold/facebook/Facebook.java: -------------------------------------------------------------------------------- 1 | package com.defold.facebook; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.ArrayList; 9 | import java.util.Set; 10 | 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | import android.app.Activity; 15 | import android.content.Intent; 16 | import android.os.Bundle; 17 | import android.os.Handler; 18 | import android.os.Message; 19 | import android.os.Messenger; 20 | import android.util.Log; 21 | 22 | import com.facebook.AccessToken; 23 | import com.facebook.login.LoginManager; 24 | import com.facebook.login.DefaultAudience; 25 | import com.facebook.appevents.AppEventsLogger; 26 | import com.facebook.FacebookSdk; 27 | 28 | public class Facebook implements Handler.Callback { 29 | private static final String TAG = "defold.facebook"; 30 | 31 | public static final String INTENT_EXTRA_MESSENGER = "defold.fb.intent.extra.messenger"; 32 | public static final String INTENT_EXTRA_APP_ID = "defold.fb.intent.extra.app_id"; 33 | public static final String INTENT_EXTRA_PERMISSIONS = "defold.fb.intent.extra.permissions"; 34 | public static final String INTENT_EXTRA_AUDIENCE = "defold.fb.intent.extra.audience"; 35 | public static final String INTENT_EXTRA_DIALOGTYPE = "defold.fb.intent.extra.dialogtype"; 36 | public static final String INTENT_EXTRA_DIALOGPARAMS = "defold.fb.intent.extra.dialogparams"; 37 | 38 | public static final String MSG_KEY_ACTION = "defold.fb.msg.action"; 39 | public static final String MSG_KEY_SUCCESS = "defold.fb.msg.success"; 40 | public static final String MSG_KEY_ERROR = "defold.fb.msg.error"; 41 | public static final String MSG_KEY_STATE = "defold.fb.msg.state"; 42 | public static final String MSG_KEY_USER = "defold.fb.msg.user"; 43 | public static final String MSG_KEY_ACCESS_TOKEN = "defold.fb.msg.access_token"; 44 | public static final String MSG_KEY_DIALOG_RESULT = "defold.fb.msg.dialog_result"; 45 | 46 | public static final String ACTION_LOGIN = "defold.fb.intent.action.LOGIN"; 47 | public static final String ACTION_REQ_READ_PERMS = "defold.fb.intent.action.REQ_READ_PERMS"; 48 | public static final String ACTION_REQ_PUB_PERMS = "defold.fb.intent.action.REQ_PUB_PERMS"; 49 | public static final String ACTION_SHOW_DIALOG = "defold.fb.intent.action.SHOW_DIALOG"; 50 | public static final String ACTION_UPDATE_METABLE = "defold.fb.intent.action.UPDATE_METABLE"; 51 | 52 | public static final String ACTION_LOGIN_WITH_PERMISSIONS = "defold.fb.intent.action.LOGIN_WITH_PERMISSIONS"; 53 | 54 | private Handler handler; 55 | private Messenger messenger; 56 | private String appId; 57 | private Callback callback; 58 | private LoginCallback loginCallback; 59 | private StateCallback stateCallback; 60 | private DialogCallback dialogCallback; 61 | private Map me; 62 | private String accessToken; 63 | private Activity activity; 64 | private AppEventsLogger eventLogger; 65 | 66 | public interface Callback { 67 | void onDone(String error); 68 | } 69 | 70 | public interface LoginCallback { 71 | void onDone(int state, String error); 72 | } 73 | 74 | public interface StateCallback { 75 | void onDone(int state, String error); 76 | } 77 | 78 | public interface DialogCallback { 79 | void onDone(Bundle result, String error); 80 | } 81 | 82 | public Facebook(Activity activity, String appId) { 83 | this.appId = appId; 84 | this.me = null; 85 | this.accessToken = null; 86 | this.activity = activity; 87 | this.eventLogger = AppEventsLogger.newLogger(this.activity); 88 | } 89 | 90 | private void startActivity(String action) { 91 | startActivity(action, null); 92 | } 93 | 94 | private void startActivity(String action, Bundle extras) { 95 | 96 | if (this.messenger == null) { 97 | this.handler = new Handler(this); 98 | this.messenger = new Messenger(this.handler); 99 | } 100 | 101 | 102 | Intent intent = new Intent(activity, FacebookActivity.class); 103 | intent.putExtra(INTENT_EXTRA_MESSENGER, messenger); 104 | intent.putExtra(INTENT_EXTRA_APP_ID, appId); 105 | if (extras != null) { 106 | intent.putExtras(extras); 107 | } 108 | intent.setAction(action); 109 | activity.startActivity(intent); 110 | } 111 | 112 | public void logout() { 113 | if (AccessToken.getCurrentAccessToken() != null) { 114 | LoginManager.getInstance().logOut(); 115 | } 116 | 117 | this.me = null; 118 | this.accessToken = null; 119 | } 120 | 121 | public String getAccessToken() { 122 | if (AccessToken.getCurrentAccessToken() != null && !AccessToken.getCurrentAccessToken().isDataAccessExpired()) { 123 | return AccessToken.getCurrentAccessToken().getToken(); 124 | } 125 | return null; 126 | } 127 | 128 | public List getPermissions() { 129 | if (AccessToken.getCurrentAccessToken() != null) { 130 | return new ArrayList(AccessToken.getCurrentAccessToken().getPermissions()); 131 | } 132 | 133 | return Collections. emptyList(); 134 | } 135 | 136 | public void loginWithPermissions(String[] permissions, int audience, LoginCallback callback) { 137 | this.loginCallback = callback; 138 | 139 | Bundle extras = new Bundle(); 140 | extras.putInt(INTENT_EXTRA_AUDIENCE, audience); 141 | extras.putStringArray(INTENT_EXTRA_PERMISSIONS, permissions); 142 | 143 | startActivity(ACTION_LOGIN_WITH_PERMISSIONS, extras); 144 | } 145 | 146 | public void showDialog(String dialogType, Bundle params, DialogCallback cb) { 147 | 148 | this.dialogCallback = cb; 149 | Bundle extras = new Bundle(); 150 | extras.putString(INTENT_EXTRA_DIALOGTYPE, dialogType); 151 | extras.putBundle(INTENT_EXTRA_DIALOGPARAMS, params); 152 | startActivity(ACTION_SHOW_DIALOG, extras); 153 | } 154 | 155 | public void postEvent(String event, double valueToSum, String[] keys, String[] values) { 156 | try { 157 | if (this.eventLogger != null) { 158 | if (keys.length == 0) { 159 | this.eventLogger.logEvent(event, valueToSum); 160 | } else { 161 | Bundle bundle = new Bundle(keys.length); 162 | for (int i = 0; i < keys.length; ++i) { 163 | bundle.putString(keys[i], values[i]); 164 | } 165 | 166 | this.eventLogger.logEvent(event, valueToSum, bundle); 167 | } 168 | } 169 | } catch (RuntimeException exception) { 170 | Log.e(TAG, "Unable to post event to Facebook Analytics"); 171 | } catch (Exception exception) { 172 | Log.e(TAG, "An error occurred while attempting to post event to Facebook Analytics"); 173 | } catch (Throwable throwable) { 174 | Log.e(TAG, "A critical error occurred while attempting to post event to Facebook Analytics"); 175 | } 176 | } 177 | 178 | public String getSdkVersion() { 179 | return FacebookSdk.getSdkVersion(); 180 | } 181 | 182 | public void disableEventUsage() { 183 | FacebookSdk.setLimitEventAndDataUsage(this.activity, true); 184 | } 185 | 186 | public void enableEventUsage() { 187 | FacebookSdk.setLimitEventAndDataUsage(this.activity, false); 188 | } 189 | 190 | @Override 191 | public boolean handleMessage(Message msg) { 192 | boolean handled = false; 193 | Bundle data = msg.getData(); 194 | String error = null; 195 | boolean success = data.containsKey(MSG_KEY_SUCCESS); 196 | if (data.containsKey(MSG_KEY_ERROR)) { 197 | error = data.getString(MSG_KEY_ERROR); 198 | } 199 | String action = data.getString(MSG_KEY_ACTION); 200 | if (action.equals(ACTION_LOGIN) || action.equals(ACTION_UPDATE_METABLE)) { 201 | if (success && data.getString(MSG_KEY_USER) != null) { 202 | JSONObject me; 203 | try { 204 | me = new JSONObject(data.getString(MSG_KEY_USER)); 205 | Iterator it = me.keys(); 206 | Map meMap = new HashMap(); 207 | while (it.hasNext()) { 208 | String key = it.next().toString(); 209 | String val = me.getString(key); 210 | meMap.put(key, val); 211 | } 212 | this.me = meMap; 213 | this.accessToken = data.getString(MSG_KEY_ACCESS_TOKEN); 214 | } catch (JSONException e) { 215 | Log.wtf(TAG, e); 216 | } 217 | } 218 | 219 | if (action.equals(ACTION_LOGIN)) { 220 | this.stateCallback.onDone(data.getInt(MSG_KEY_STATE), error); 221 | } 222 | } else if (action.equals(ACTION_LOGIN_WITH_PERMISSIONS)) { 223 | this.loginCallback.onDone(data.getInt(MSG_KEY_STATE), error); 224 | } else if (action.equals(ACTION_SHOW_DIALOG)) { 225 | this.dialogCallback.onDone(data.getBundle(MSG_KEY_DIALOG_RESULT), error); 226 | } 227 | return handled; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /facebook/src/java/com/defold/facebook/FacebookActivity.java: -------------------------------------------------------------------------------- 1 | package com.defold.facebook; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import android.app.Activity; 8 | import android.content.Intent; 9 | import android.os.Bundle; 10 | import android.os.Message; 11 | import android.os.Messenger; 12 | import android.os.RemoteException; 13 | import android.util.Log; 14 | import android.net.Uri; 15 | 16 | import androidx.fragment.app.FragmentActivity; 17 | import android.content.Context; 18 | 19 | import com.facebook.FacebookSdk; 20 | import com.facebook.AccessToken; 21 | import com.facebook.CallbackManager; 22 | import com.facebook.FacebookCallback; 23 | import com.facebook.FacebookException; 24 | import com.facebook.login.LoginManager; 25 | import com.facebook.login.LoginResult; 26 | import com.facebook.login.DefaultAudience; 27 | import com.facebook.share.Sharer; 28 | import com.facebook.share.model.ShareLinkContent; 29 | import com.facebook.share.model.GameRequestContent; 30 | import com.facebook.share.widget.ShareDialog; 31 | import com.facebook.share.widget.GameRequestDialog; 32 | import com.facebook.GraphRequest; 33 | import com.facebook.GraphResponse; 34 | import com.facebook.HttpMethod; 35 | 36 | import org.json.JSONObject; 37 | 38 | public class FacebookActivity extends Activity { 39 | private static final String TAG = "defold.facebook"; 40 | 41 | private Messenger messenger; 42 | private CallbackManager callbackManager; 43 | private Message onAbortedMessage; 44 | 45 | // Must match values from facebook.h 46 | private enum State { 47 | STATE_CREATED (1), 48 | STATE_CREATED_TOKEN_LOADED (2), 49 | STATE_CREATED_OPENING (3), 50 | STATE_OPEN (4), 51 | STATE_OPEN_TOKEN_EXTENDED (5), 52 | STATE_CLOSED_LOGIN_FAILED (6), 53 | STATE_CLOSED (7); 54 | 55 | private final int value; 56 | private State(int value) { this.value = value; }; 57 | public int getValue() { return this.value; }; 58 | 59 | } 60 | 61 | /* 62 | * Functions to convert Lua enums to corresponding Facebook enums. 63 | * Switch values are from enums defined in facebook_android.cpp. 64 | */ 65 | private DefaultAudience convertDefaultAudience(int fromLuaInt) { 66 | switch (fromLuaInt) { 67 | case 2: 68 | return DefaultAudience.ONLY_ME; 69 | case 3: 70 | return DefaultAudience.FRIENDS; 71 | case 4: 72 | return DefaultAudience.EVERYONE; 73 | case 1: 74 | default: 75 | return DefaultAudience.NONE; 76 | } 77 | } 78 | 79 | private GameRequestContent.ActionType convertGameRequestAction(int fromLuaInt) { 80 | switch (fromLuaInt) { 81 | case 3: 82 | return GameRequestContent.ActionType.ASKFOR; 83 | case 4: 84 | return GameRequestContent.ActionType.TURN; 85 | case 2: 86 | default: 87 | return GameRequestContent.ActionType.SEND; 88 | } 89 | } 90 | 91 | private GameRequestContent.Filters convertGameRequestFilters(int fromLuaInt) { 92 | switch (fromLuaInt) { 93 | case 3: 94 | return GameRequestContent.Filters.APP_NON_USERS; 95 | case 2: 96 | default: 97 | return GameRequestContent.Filters.APP_USERS; 98 | } 99 | } 100 | 101 | private class DefaultDialogCallback implements FacebookCallback { 102 | 103 | @Override 104 | public void onSuccess(T result) { /* needs to be implemented for each dialog */ } 105 | 106 | @Override 107 | public void onCancel() { 108 | Bundle data = new Bundle(); 109 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 110 | data.putString(Facebook.MSG_KEY_ERROR, "Dialog canceled"); 111 | respond(Facebook.ACTION_SHOW_DIALOG, data); 112 | } 113 | 114 | @Override 115 | public void onError(FacebookException error) { 116 | Bundle data = new Bundle(); 117 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 118 | data.putString(Facebook.MSG_KEY_ERROR, error.toString()); 119 | respond(Facebook.ACTION_SHOW_DIALOG, data); 120 | } 121 | 122 | } 123 | 124 | private void respond(final String action, final Bundle data) { 125 | onAbortedMessage = null; // only kept for when respond() never happens. 126 | final Activity activity = this; 127 | this.runOnUiThread(new Runnable() { 128 | @Override 129 | public void run() { 130 | data.putString(Facebook.MSG_KEY_ACTION, action); 131 | Message message = new Message(); 132 | message.setData(data); 133 | try { 134 | messenger.send(message); 135 | } catch (RemoteException e) { 136 | Log.wtf(TAG, e); 137 | } 138 | activity.finish(); 139 | } 140 | }); 141 | } 142 | 143 | // Do a graph request to get latest user data (i.e. "me" table) 144 | private void UpdateUserData(final Boolean triggerLoginCallback) { 145 | GraphRequest request = GraphRequest.newMeRequest( 146 | AccessToken.getCurrentAccessToken(), 147 | new GraphRequest.GraphJSONObjectCallback() { 148 | @Override 149 | public void onCompleted(JSONObject object, GraphResponse response) { 150 | Bundle data = new Bundle(); 151 | if (response.getError() == null) { 152 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, true); 153 | data.putString(Facebook.MSG_KEY_USER, object.toString()); 154 | data.putString(Facebook.MSG_KEY_ACCESS_TOKEN, AccessToken.getCurrentAccessToken().getToken()); 155 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_OPEN.getValue()); 156 | } else { 157 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 158 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_CLOSED_LOGIN_FAILED.getValue()); 159 | data.putString(Facebook.MSG_KEY_ERROR, response.getError().getErrorMessage()); 160 | } 161 | 162 | if (triggerLoginCallback) { 163 | respond(Facebook.ACTION_LOGIN, data); 164 | } else { 165 | respond(Facebook.ACTION_UPDATE_METABLE, data); 166 | } 167 | } 168 | }); 169 | Bundle parameters = new Bundle(); 170 | parameters.putString("fields", "last_name,link,id,gender,email,locale,name,first_name,updated_time"); 171 | request.setParameters(parameters); 172 | request.executeAsync(); 173 | } 174 | 175 | private void actionLogin() { 176 | 177 | if (AccessToken.getCurrentAccessToken() != null) { 178 | UpdateUserData(true); 179 | } else { 180 | LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback() { 181 | 182 | @Override 183 | public void onSuccess(LoginResult result) { 184 | UpdateUserData(true); 185 | } 186 | 187 | @Override 188 | public void onCancel() { 189 | Bundle data = new Bundle(); 190 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 191 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_CLOSED_LOGIN_FAILED.getValue()); 192 | data.putString(Facebook.MSG_KEY_ERROR, "Login canceled"); 193 | respond(Facebook.ACTION_LOGIN, data); 194 | } 195 | 196 | @Override 197 | public void onError(FacebookException error) { 198 | Bundle data = new Bundle(); 199 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 200 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_CLOSED_LOGIN_FAILED.getValue()); 201 | data.putString(Facebook.MSG_KEY_ERROR, error.toString()); 202 | respond(Facebook.ACTION_LOGIN, data); 203 | } 204 | 205 | }); 206 | 207 | LoginManager.getInstance().logInWithReadPermissions(this, Arrays.asList("public_profile", "email", "user_friends")); 208 | } 209 | 210 | } 211 | 212 | private void actionLoginWithPermissions(final String action, final Bundle extras) { 213 | final String[] permissions = extras.getStringArray(Facebook.INTENT_EXTRA_PERMISSIONS); 214 | LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback() { 215 | 216 | @Override 217 | public void onSuccess(LoginResult result) { 218 | Bundle data = new Bundle(); 219 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_OPEN.getValue()); 220 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, true); 221 | respond(action, data); 222 | } 223 | 224 | @Override 225 | public void onCancel() { 226 | Bundle data = new Bundle(); 227 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 228 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_CLOSED_LOGIN_FAILED.getValue()); 229 | data.putString(Facebook.MSG_KEY_ERROR, "Login was cancelled"); 230 | respond(action, data); 231 | } 232 | 233 | @Override 234 | public void onError(FacebookException error) { 235 | Bundle data = new Bundle(); 236 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 237 | data.putInt(Facebook.MSG_KEY_STATE, State.STATE_CLOSED_LOGIN_FAILED.getValue()); 238 | data.putString(Facebook.MSG_KEY_ERROR, error.toString()); 239 | respond(action, data); 240 | } 241 | 242 | }); 243 | 244 | if (action.equals(Facebook.ACTION_LOGIN_WITH_PERMISSIONS)) { 245 | int audience = extras.getInt(Facebook.INTENT_EXTRA_AUDIENCE); 246 | LoginManager.getInstance().setDefaultAudience(convertDefaultAudience(audience)); 247 | 248 | // If data access has expired, we reauthorize instead of calling regular login. 249 | if (AccessToken.getCurrentAccessToken() != null && AccessToken.getCurrentAccessToken().isDataAccessExpired()) { 250 | LoginManager.getInstance().reauthorizeDataAccess(this); 251 | return; 252 | } 253 | LoginManager.getInstance().logIn(this, Arrays.asList(permissions)); 254 | } 255 | } 256 | 257 | private void actionRequestPermissions( final String action, final Bundle extras ) { 258 | final String[] permissions = extras.getStringArray(Facebook.INTENT_EXTRA_PERMISSIONS); 259 | LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback() { 260 | 261 | @Override 262 | public void onSuccess(LoginResult result) { 263 | Bundle data = new Bundle(); 264 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, true); 265 | UpdateUserData(false); 266 | respond(action, data); 267 | } 268 | 269 | @Override 270 | public void onCancel() { 271 | Bundle data = new Bundle(); 272 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 273 | data.putString(Facebook.MSG_KEY_ERROR, "Login canceled"); 274 | respond(action, data); 275 | } 276 | 277 | @Override 278 | public void onError(FacebookException error) { 279 | Bundle data = new Bundle(); 280 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, false); 281 | data.putString(Facebook.MSG_KEY_ERROR, error.toString()); 282 | respond(action, data); 283 | } 284 | 285 | }); 286 | 287 | if (action.equals(Facebook.ACTION_REQ_READ_PERMS)) { 288 | LoginManager.getInstance().logInWithReadPermissions(this, Arrays.asList(permissions)); 289 | } else { 290 | int defaultAudienceInt = extras.getInt(Facebook.INTENT_EXTRA_AUDIENCE); 291 | LoginManager.getInstance().setDefaultAudience(convertDefaultAudience(defaultAudienceInt)); 292 | LoginManager.getInstance().logIn(this, Arrays.asList(permissions)); 293 | } 294 | } 295 | 296 | private void actionShowDialog( final Bundle extras ) { 297 | 298 | String dialogType = extras.getString(Facebook.INTENT_EXTRA_DIALOGTYPE); 299 | Bundle dialogParams = extras.getBundle(Facebook.INTENT_EXTRA_DIALOGPARAMS); 300 | 301 | // All dialogs that require login 302 | if (AccessToken.getCurrentAccessToken() == null) { 303 | 304 | Bundle data = new Bundle(); 305 | data.putString(Facebook.MSG_KEY_ERROR, "User is not logged in"); 306 | respond(Facebook.ACTION_SHOW_DIALOG, data); 307 | 308 | } else { 309 | 310 | if (dialogType.equals("feed")) { 311 | 312 | ShareDialog shareDialog = new ShareDialog(this); 313 | ShareLinkContent.Builder content = new ShareLinkContent.Builder() 314 | .setContentUrl(Uri.parse(dialogParams.getString("link", ""))) 315 | .setPlaceId(dialogParams.getString("place_id", "")) 316 | .setRef(dialogParams.getString("ref", "")); 317 | 318 | String peopleIdsString = dialogParams.getString("people_ids", null); 319 | if (peopleIdsString != null) { 320 | content.setPeopleIds(Arrays.asList(peopleIdsString.split(","))); 321 | } 322 | 323 | shareDialog.registerCallback(callbackManager, new DefaultDialogCallback() { 324 | 325 | @Override 326 | public void onSuccess(Sharer.Result result) { 327 | Bundle data = new Bundle(); 328 | Bundle subdata = new Bundle(); 329 | subdata.putString("post_id", result.getPostId()); 330 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, true); 331 | data.putBundle(Facebook.MSG_KEY_DIALOG_RESULT, subdata); 332 | respond(Facebook.ACTION_SHOW_DIALOG, data); 333 | } 334 | 335 | }); 336 | 337 | shareDialog.show(this, content.build()); 338 | 339 | } else if (dialogType.equals("apprequests") || dialogType.equals("apprequest")) { 340 | 341 | GameRequestDialog gameRequestDialog = new GameRequestDialog(this); 342 | 343 | ArrayList suggestionsArray = new ArrayList(); 344 | String suggestionsString = dialogParams.getString("suggestions", null); 345 | if (suggestionsString != null) { 346 | String [] suggestions = suggestionsString.split(","); 347 | suggestionsArray.addAll(Arrays.asList(suggestions)); 348 | } 349 | 350 | // comply with JS way of specifying recipients/to 351 | String toString = null; 352 | 353 | // Recipients field does not exist in FB SDK 4.3 for Android. 354 | // For now we fill the "to" field with the comma separated user ids, 355 | // but when we upgrade to a newer version with the recipients field 356 | // we should fill the correct recipients field instead. 357 | if (dialogParams.getString("to", null) != null) { 358 | toString = dialogParams.getString("to"); 359 | } 360 | 361 | if (dialogParams.getString("recipients", null) != null) { 362 | toString = dialogParams.getString("recipients"); 363 | } 364 | 365 | GameRequestContent.Builder content = new GameRequestContent.Builder() 366 | .setTitle(dialogParams.getString("title", "")) 367 | .setMessage(dialogParams.getString("message", "")) 368 | .setData(dialogParams.getString("data")); 369 | 370 | if (dialogParams.getString("object_id") != null) { 371 | content.setObjectId(dialogParams.getString("object_id")); 372 | } 373 | 374 | if (dialogParams.getString("action_type") != null) { 375 | int actionInt = Integer.parseInt(dialogParams.getString("action_type", "0")); 376 | content.setActionType(convertGameRequestAction(actionInt)); 377 | } 378 | 379 | // recipients, filters and suggestions are mutually exclusive 380 | int filters = Integer.parseInt(dialogParams.getString("filters", "-1")); 381 | if (toString != null) { 382 | content.setTo(toString); 383 | } else if (filters != -1) { 384 | content.setFilters(convertGameRequestFilters(filters)); 385 | } else { 386 | content.setSuggestions(suggestionsArray); 387 | } 388 | 389 | gameRequestDialog.registerCallback(callbackManager, new DefaultDialogCallback() { 390 | 391 | @Override 392 | public void onSuccess(GameRequestDialog.Result result) { 393 | final String[] recipients = result.getRequestRecipients().toArray(new String[result.getRequestRecipients().size()]); 394 | Bundle data = new Bundle(); 395 | Bundle subdata = new Bundle(); 396 | subdata.putString("request_id", result.getRequestId()); 397 | subdata.putStringArray("to", recipients); 398 | data.putBoolean(Facebook.MSG_KEY_SUCCESS, true); 399 | data.putBundle(Facebook.MSG_KEY_DIALOG_RESULT, subdata); 400 | respond(Facebook.ACTION_SHOW_DIALOG, data); 401 | } 402 | 403 | }); 404 | 405 | gameRequestDialog.show(this, content.build()); 406 | 407 | } else { 408 | Bundle data = new Bundle(); 409 | data.putString(Facebook.MSG_KEY_ERROR, "Unknown dialog type"); 410 | respond(Facebook.ACTION_SHOW_DIALOG, data); 411 | } 412 | } 413 | } 414 | 415 | @Override 416 | public void onCreate(Bundle savedInstanceState) { 417 | super.onCreate(savedInstanceState); 418 | Intent intent = this.getIntent(); 419 | Bundle extras = intent.getExtras(); 420 | 421 | callbackManager = CallbackManager.Factory.create(); 422 | 423 | final String action = intent.getAction(); 424 | this.messenger = (Messenger) extras.getParcelable(Facebook.INTENT_EXTRA_MESSENGER); 425 | 426 | // Prepare a response to send in case we finish without having sent anything 427 | // (activity shut down etc). This is cleared by respond() 428 | Bundle abortedData = new Bundle(); 429 | abortedData.putString(Facebook.MSG_KEY_ACTION, action); 430 | abortedData.putString(Facebook.MSG_KEY_ERROR, "Aborted"); 431 | onAbortedMessage = new Message(); 432 | onAbortedMessage.setData(abortedData); 433 | 434 | try { 435 | if (action.equals(Facebook.ACTION_LOGIN)) { 436 | actionLogin(); 437 | } else if (action.equals(Facebook.ACTION_REQ_READ_PERMS) || 438 | action.equals(Facebook.ACTION_REQ_PUB_PERMS)) { 439 | actionRequestPermissions( action, extras ); 440 | } else if (action.equals(Facebook.ACTION_LOGIN_WITH_PERMISSIONS)) { 441 | actionLoginWithPermissions(action, extras); 442 | } else if (action.equals(Facebook.ACTION_SHOW_DIALOG)) { 443 | actionShowDialog( extras ); 444 | } 445 | } catch (Exception e) { 446 | Bundle data = new Bundle(); 447 | data.putString(Facebook.MSG_KEY_ERROR, e.getMessage()); 448 | respond(action, data); 449 | } 450 | } 451 | 452 | @Override 453 | public void onDestroy() { 454 | super.onDestroy(); 455 | if (onAbortedMessage != null) { 456 | try { 457 | messenger.send(onAbortedMessage); 458 | } catch (RemoteException e) { 459 | Log.wtf(TAG, e); 460 | } 461 | } 462 | } 463 | 464 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 465 | super.onActivityResult(requestCode, resultCode, data); 466 | callbackManager.onActivityResult(requestCode, resultCode, data); 467 | } 468 | 469 | } 470 | -------------------------------------------------------------------------------- /facebook/src/java/com/defold/facebook/FacebookJNI.java: -------------------------------------------------------------------------------- 1 | package com.defold.facebook; 2 | 3 | import java.net.URLEncoder; 4 | 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.Iterator; 8 | import java.util.Collection; 9 | import java.util.Arrays; 10 | import java.util.concurrent.CountDownLatch; 11 | 12 | import android.app.Activity; 13 | import android.util.Log; 14 | import android.os.Build; 15 | import android.os.Bundle; 16 | 17 | import com.facebook.FacebookSdk; 18 | import com.facebook.appevents.AppEventsLogger; 19 | import com.facebook.login.DefaultAudience; 20 | import com.facebook.applinks.AppLinkData; 21 | import com.facebook.internal.BundleJSONConverter; 22 | 23 | import org.json.JSONException; 24 | import org.json.JSONObject; 25 | import org.json.JSONArray; 26 | 27 | import java.util.concurrent.*; 28 | import java.util.concurrent.atomic.AtomicInteger; 29 | 30 | // A helper class that initializes the facebook sdk, and also activates/deactivates the app 31 | class FacebookAppJNI { 32 | 33 | private static final String TAG = "defold.facebook"; 34 | 35 | private Activity activity; 36 | private String appId; 37 | 38 | // fb sdk patch for API < 11 support 39 | // Custom Executor for FB SDK 40 | private static final Object LOCK = new Object(); 41 | // This code is copied from the android.os.AsyncTask implementation to set up the executor with the same parameters 42 | // as the one introduced in API 11 43 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 44 | private static final int CORE_POOL_SIZE = CPU_COUNT + 1; 45 | private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 46 | private static final int KEEP_ALIVE = 1; 47 | 48 | private static final ThreadFactory sThreadFactory = new ThreadFactory() { 49 | private final AtomicInteger mCount = new AtomicInteger(1); 50 | public Thread newThread(Runnable r) { 51 | return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); 52 | } 53 | }; 54 | private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue(128); 55 | 56 | private static Executor customFacebookExecutor; 57 | 58 | private static Executor getCustomExecutor() { 59 | synchronized (LOCK) { 60 | if (customFacebookExecutor == null) { 61 | customFacebookExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, 62 | TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 63 | } 64 | } 65 | return customFacebookExecutor; 66 | } 67 | 68 | public FacebookAppJNI(Activity activity, String appId) { 69 | this.activity = activity; 70 | this.appId = appId; 71 | 72 | final CountDownLatch latch = new CountDownLatch(1); 73 | 74 | // Workaround for old devices (pre API 11) 75 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 76 | FacebookSdk.setExecutor(getCustomExecutor()); 77 | } 78 | 79 | this.activity.runOnUiThread(new Runnable() { 80 | @Override 81 | public void run() { 82 | String s = String.format("sdkInitialize: activity %s appid: %s", FacebookAppJNI.this.activity, FacebookAppJNI.this.appId); 83 | Log.d(TAG, s); 84 | FacebookSdk.setApplicationId( FacebookAppJNI.this.appId ); 85 | FacebookSdk.sdkInitialize( FacebookAppJNI.this.activity ); 86 | latch.countDown(); 87 | } 88 | }); 89 | 90 | try { 91 | latch.await(); 92 | } catch (InterruptedException ex) { 93 | } 94 | } 95 | 96 | public void activate() { 97 | String s = String.format("activateApp: activity %s appid: %s", this.activity, this.appId); 98 | Log.d(TAG, s); 99 | AppEventsLogger.activateApp(this.activity.getApplication(), this.appId); 100 | } 101 | 102 | public void deactivate() { 103 | // Do nothing, since AppEventsLogger.deactivateApp() was deprecated 104 | } 105 | } 106 | 107 | class FacebookJNI { 108 | 109 | private static final String TAG = "defold.facebook"; 110 | 111 | private native void onLoginWithPermissions(long userData, int state, String error); 112 | 113 | private native void onDialogComplete(long userData, String results, String error); 114 | 115 | private native void onIterateMeEntry(long userData, String key, String value); 116 | 117 | private native void onIteratePermissionsEntry(long userData, String permission); 118 | 119 | private native void onFetchDeferredAppLinkData(long userData, String results, Boolean isError); 120 | 121 | // JSONObject.wrap not available in Java 7, 122 | // Using https://android.googlesource.com/platform/libcore/+/master/json/src/main/java/org/json/JSONObject.java 123 | private static Object JSONWrap(Object o) { 124 | if (o == null) { 125 | return null; 126 | } 127 | if (o instanceof JSONArray || o instanceof JSONObject) { 128 | return o; 129 | } 130 | if (o.equals(null)) { 131 | return o; 132 | } 133 | try { 134 | if (o instanceof Collection) { 135 | return new JSONArray((Collection) o); 136 | } else if (o.getClass().isArray()) { 137 | return new JSONArray(Arrays.asList((Object[])o)); 138 | } 139 | if (o instanceof Map) { 140 | return new JSONObject((Map) o); 141 | } 142 | if (o instanceof Boolean || 143 | o instanceof Byte || 144 | o instanceof Character || 145 | o instanceof Double || 146 | o instanceof Float || 147 | o instanceof Integer || 148 | o instanceof Long || 149 | o instanceof Short || 150 | o instanceof String) { 151 | return o; 152 | } 153 | if (o.getClass().getPackage().getName().startsWith("java.")) { 154 | return o.toString(); 155 | } 156 | } catch (Exception ignored) { 157 | } 158 | return null; 159 | } 160 | 161 | // From http://stackoverflow.com/questions/21858528/convert-a-bundle-to-json 162 | private static JSONObject bundleToJson(final Bundle in) throws JSONException { 163 | JSONObject json = new JSONObject(); 164 | if (in == null) { 165 | return json; 166 | } 167 | Set keys = in.keySet(); 168 | for (String key : keys) { 169 | json.put(key, JSONWrap(in.get(key))); 170 | } 171 | 172 | return json; 173 | } 174 | 175 | private Facebook facebook; 176 | private Activity activity; 177 | 178 | public FacebookJNI(Activity activity, String appId) { 179 | this.activity = activity; 180 | this.facebook = new Facebook(this.activity, appId); 181 | } 182 | 183 | public void logout() { 184 | this.activity.runOnUiThread(new Runnable() { 185 | @Override 186 | public void run() { 187 | FacebookJNI.this.facebook.logout(); 188 | } 189 | }); 190 | } 191 | 192 | public void iteratePermissions(final long userData) { 193 | for (String permission : this.facebook.getPermissions()) { 194 | onIteratePermissionsEntry(userData, permission); 195 | } 196 | } 197 | 198 | public String getAccessToken() { 199 | return this.facebook.getAccessToken(); 200 | } 201 | 202 | public String getSdkVersion() { 203 | return this.facebook.getSdkVersion(); 204 | } 205 | 206 | public void loginWithPermissions(final long userData, final int audience, final String permissions) { 207 | this.activity.runOnUiThread(new Runnable() { 208 | 209 | @Override 210 | public void run() { 211 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 212 | // call cb immediately with failed state 213 | // STATE_CLOSED_LOGIN_FAILED = 6 214 | onLoginWithPermissions(userData, 6, "Not supported, Android SDK too old."); 215 | } else { 216 | Facebook.LoginCallback callback = new Facebook.LoginCallback() { 217 | 218 | @Override 219 | public void onDone(final int state, final String error) { 220 | onLoginWithPermissions(userData, state, error); 221 | } 222 | 223 | }; 224 | 225 | facebook.loginWithPermissions(permissions.split(","), audience, callback); 226 | } 227 | } 228 | 229 | }); 230 | } 231 | 232 | public void showDialog(final long userData, final String dialogType, final String paramsJson) { 233 | 234 | this.activity.runOnUiThread(new Runnable() { 235 | 236 | @Override 237 | public void run() { 238 | Facebook.DialogCallback cb = new Facebook.DialogCallback() { 239 | @Override 240 | public void onDone(final Bundle result, final String error) { 241 | 242 | // serialize bundle as JSON for C/Lua side 243 | String res = null; 244 | String err = error; 245 | try { 246 | JSONObject obj = bundleToJson(result); 247 | res = obj.toString(); 248 | } catch(JSONException e) { 249 | err = "Error while converting dialog result to JSON: " + e.getMessage(); 250 | } 251 | 252 | onDialogComplete(userData, res, err); 253 | } 254 | }; 255 | 256 | // Parse dialog params from JSON and put into Bundle 257 | Bundle params = new Bundle(); 258 | JSONObject o = null; 259 | try { 260 | o = new JSONObject(paramsJson); 261 | Iterator iter = o.keys(); 262 | while (iter.hasNext()) { 263 | String key = iter.next().toString(); 264 | String value = o.get(key).toString(); 265 | params.putString(key, value); 266 | } 267 | } catch (Exception e) { 268 | String err = "Failed to get json value"; 269 | Log.wtf(TAG, err, e); 270 | onDialogComplete(userData, null, err); 271 | return; 272 | } 273 | 274 | facebook.showDialog(dialogType, params, cb); 275 | 276 | } 277 | }); 278 | 279 | } 280 | 281 | public void postEvent(String event, double valueToSum, String[] keys, String[] values) { 282 | facebook.postEvent(event, valueToSum, keys, values); 283 | } 284 | 285 | public void enableEventUsage() { 286 | facebook.disableEventUsage(); 287 | } 288 | 289 | public void disableEventUsage() { 290 | facebook.enableEventUsage(); 291 | } 292 | 293 | public void fetchDeferredAppLinkData(final long userData) { 294 | AppLinkData.fetchDeferredAppLinkData(this.activity, 295 | new AppLinkData.CompletionHandler() { 296 | @Override 297 | public void onDeferredAppLinkDataFetched(AppLinkData appLinkData) { 298 | String message = null; 299 | Boolean isError = false; 300 | JSONObject data; 301 | try { 302 | if (appLinkData == null) { 303 | message ="{}"; 304 | } else { 305 | data = new JSONObject(); 306 | data.put("ref", appLinkData.getRef()); 307 | data.put("target_url", appLinkData.getTargetUri().toString()); 308 | if (appLinkData.getArgumentBundle() != null) { 309 | data.put("extras", BundleJSONConverter.convertToJSON(appLinkData.getArgumentBundle())); 310 | } 311 | message = data.toString(); 312 | } 313 | } catch (JSONException e) { 314 | isError = true; 315 | message = "'error':'Error while converting DeferredAppLinkData to JSON:" + e.getMessage()+"'"; 316 | } 317 | onFetchDeferredAppLinkData(userData, message, isError); 318 | } 319 | }); 320 | } 321 | 322 | } 323 | -------------------------------------------------------------------------------- /facebook/test/compile_cl.bat: -------------------------------------------------------------------------------- 1 | echo on 2 | 3 | if NOT DEFINED VCINSTALLDIR ( 4 | if exist "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" ( 5 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 6 | echo "USING VISUAL STUDIO 17" 7 | ) 8 | ) 9 | 10 | if NOT DEFINED VCINSTALLDIR ( 11 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 15.0\VC\vcvarsall.bat" ( 12 | call "C:\Program Files (x86)\Microsoft Visual Studio 15.0\VC\vcvarsall.bat" amd64 13 | echo "USING VISUAL STUDIO 15" 14 | ) 15 | ) 16 | 17 | if NOT DEFINED VCINSTALLDIR ( 18 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" ( 19 | call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 20 | echo "USING VISUAL STUDIO 14" 21 | ) 22 | ) 23 | 24 | if NOT DEFINED VCINSTALLDIR ( 25 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 13.0\VC\vcvarsall.bat" ( 26 | call "C:\Program Files (x86)\Microsoft Visual Studio 13.0\VC\vcvarsall.bat" amd64 27 | echo "USING VISUAL STUDIO 13" 28 | ) 29 | ) 30 | 31 | if NOT DEFINED VCINSTALLDIR ( 32 | if exist "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" ( 33 | call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" amd64 34 | echo "USING VISUAL STUDIO 12" 35 | ) 36 | ) 37 | 38 | echo "Using " %VCINSTALLDIR% 39 | 40 | if NOT DEFINED VCINSTALLDIR ( 41 | echo "No compatible visual studio found! run vcvarsall.bat first!" 42 | ) 43 | 44 | mkdir build 45 | 46 | cl.exe /O2 /D_CRT_SECURE_NO_WARNINGS /D_HAS_EXCEPTIONS=0 /EHsc /W4 /Isrc test/test_fb.cpp /link /out:.\build\test_params.exe 47 | 48 | del *.obj 49 | -------------------------------------------------------------------------------- /facebook/test/compile_clang.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | if [ ! -e build ]; then 5 | mkdir -p build 6 | fi 7 | 8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | 10 | #OPT=-O3 11 | OPT="-g -O2" 12 | # for debugging 13 | #DISASSEMBLY="-S -masm=intel" 14 | #PREPROCESS="-E" 15 | #ASAN="-fsanitize=address -fno-omit-frame-pointer -fsanitize-address-use-after-scope -fsanitize=undefined" 16 | #ASAN_LDFLAGS="-fsanitize=address " 17 | CXXFLAGS="$CXXFLAGS -std=c++98 -Wall -Weverything -pedantic -Wno-global-constructors -Isrc -I${DIR}" 18 | LDFLAGS= 19 | ARCH=-m64 20 | 21 | function compile_test { 22 | local name=$1 23 | clang++ -o ./build/${name} $OPT $ARCH $DISASSEMBLY $CXXFLAGS $LDFLAGS -c test/${name}.cpp 24 | } 25 | 26 | time compile_test test_fb 27 | 28 | -------------------------------------------------------------------------------- /facebook/test/run_tests.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | 3 | .\build\test_params.exe 4 | .\build\test_typed_test.exe 5 | -------------------------------------------------------------------------------- /facebook/test/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | function run_tests { 6 | local pattern=$1 7 | for name in `find build -iname "${pattern}" -perm +111` 8 | do 9 | echo $name 10 | time $name 11 | done 12 | } 13 | 14 | run_tests test_* 15 | -------------------------------------------------------------------------------- /facebook/test/wscript: -------------------------------------------------------------------------------- 1 | import Task, TaskGen 2 | from TaskGen import extension, declare_extension 3 | 4 | def build(bld): 5 | 6 | if bld.env.PLATFORM in ('darwin', 'x86_64-darwin', 'linux', 'x86_64-linux', 'win32', 'x86_64-win32'): 7 | test_fb = bld.new_task_gen(features = 'cxx cprogram test', 8 | includes = '../../../src .', 9 | uselib = 'GTEST DLIB LUA SCRIPT', 10 | uselib_local = 'facebookext_test', 11 | target = 'test_fb', 12 | source = 'test_fb.cpp') 13 | 14 | test_fb.install_path = None 15 | -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = extension-facebook 3 | dependencies#0 = https://github.com/andsve/dirtylarry/archive/master.zip 4 | 5 | [bootstrap] 6 | main_collection = /main/main.collectionc 7 | 8 | [script] 9 | shared_state = 1 10 | 11 | [display] 12 | width = 375 13 | height = 667 14 | high_dpi = 1 15 | 16 | [ios] 17 | bundle_identifier = com.defold.facebook 18 | 19 | [android] 20 | package = com.defold.facebook 21 | 22 | [library] 23 | include_dirs = facebook 24 | 25 | [facebook] 26 | appid = 1609643756005877 27 | autoinit = 0 28 | clienttoken = ffb21570434787084bbdd8d6042bc9a1 29 | 30 | [native_extension] 31 | app_manifest = /generated.appmanifest 32 | -------------------------------------------------------------------------------- /generated.appmanifest: -------------------------------------------------------------------------------- 1 | # App manifest generated Fri Nov 20 2020 09:28:33 GMT+0100 (Central European Standard Time) 2 | # Settings: OpenGL 3 | platforms: 4 | x86_64-osx: 5 | context: 6 | excludeLibs: [] 7 | excludeSymbols: [] 8 | symbols: [] 9 | libs: [] 10 | frameworks: [] 11 | linkFlags: [] 12 | 13 | x86_64-linux: 14 | context: 15 | excludeLibs: [] 16 | excludeSymbols: [] 17 | symbols: [] 18 | libs: [] 19 | linkFlags: [] 20 | 21 | js-web: 22 | context: 23 | excludeLibs: [] 24 | excludeJsLibs: [] 25 | excludeSymbols: [] 26 | symbols: [] 27 | libs: [] 28 | linkFlags: [] 29 | 30 | wasm-web: 31 | context: 32 | excludeLibs: [] 33 | excludeJsLibs: [] 34 | excludeSymbols: [] 35 | symbols: [] 36 | libs: [] 37 | linkFlags: [] 38 | 39 | x86-win32: 40 | context: 41 | excludeLibs: [] 42 | excludeSymbols: [] 43 | symbols: [] 44 | libs: [] 45 | linkFlags: [] 46 | 47 | x86_64-win32: 48 | context: 49 | excludeLibs: [] 50 | excludeSymbols: [] 51 | symbols: [] 52 | libs: [] 53 | linkFlags: [] 54 | 55 | armv7-android: 56 | context: 57 | excludeLibs: [] 58 | excludeJars: [] 59 | excludeSymbols: [] 60 | symbols: [] 61 | libs: [] 62 | linkFlags: [] 63 | jetifier: true 64 | 65 | arm64-android: 66 | context: 67 | excludeLibs: [] 68 | excludeJars: [] 69 | excludeSymbols: [] 70 | symbols: [] 71 | libs: [] 72 | linkFlags: [] 73 | jetifier: true 74 | 75 | armv7-ios: 76 | context: 77 | excludeLibs: [] 78 | excludeSymbols: [] 79 | symbols: [] 80 | libs: [] 81 | frameworks: [] 82 | linkFlags: [] 83 | 84 | arm64-ios: 85 | context: 86 | excludeLibs: [] 87 | excludeSymbols: [] 88 | symbols: [] 89 | libs: [] 90 | frameworks: [] 91 | linkFlags: [] 92 | 93 | x86_64-ios: 94 | context: 95 | excludeLibs: [] 96 | excludeSymbols: [] 97 | symbols: [] 98 | libs: [] 99 | frameworks: [] 100 | linkFlags: [] 101 | -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | mouse_trigger { 2 | input: MOUSE_BUTTON_1 3 | action: "touch" 4 | } 5 | -------------------------------------------------------------------------------- /main/facebook.gui_script: -------------------------------------------------------------------------------- 1 | local dirtylarry = require "dirtylarry/dirtylarry" 2 | 3 | local function update_access_token(self) 4 | if facebook and facebook.access_token() then 5 | gui.set_text(gui.get_node("access_token"), "Access token found") 6 | else 7 | gui.set_text(gui.get_node("access_token"), "") 8 | end 9 | end 10 | 11 | local function update_version(self) 12 | local n = gui.get_node("version") 13 | gui.set_text(n, "v." .. (facebook and facebook.get_version() or "")) 14 | end 15 | 16 | function init(self) 17 | gui.set_render_order(15) 18 | self.root = gui.get_node("root") 19 | msg.post("#", "acquire_input_focus") 20 | end 21 | 22 | function on_message(self, message_id, message, sender) 23 | -- screen movement 24 | move_root(self, message_id, message) 25 | end 26 | 27 | local function fb_login(self, data) 28 | pprint("------------------- fb_login(self, data) -----------------------") 29 | if data.status == facebook.STATE_OPEN then 30 | gui.set_text(gui.get_node("status"), "fb_login - facebook.STATE_OPEN") 31 | end 32 | 33 | if data.status == facebook.STATE_CLOSED_LOGIN_FAILED then 34 | gui.set_text(gui.get_node("status"), "fb_login - facebook.STATE_CLOSED_LOGIN_FAILED") 35 | end 36 | 37 | if data.error then 38 | gui.set_text(gui.get_node("error"), "fb_login - " .. tostring(data.error)) 39 | else 40 | gui.set_text(gui.get_node("error"), "fb_login - no error") 41 | end 42 | update_access_token(self) 43 | end 44 | 45 | local function fb_login_limited(self, data) 46 | pprint("------------------- fb_login(self, data) -----------------------") 47 | if data.status == facebook.STATE_OPEN then 48 | local authentication_token = facebook.get_current_authentication_token() 49 | local current_profile = facebook.get_current_profile() 50 | pprint("authentication_token:", authentication_token) 51 | pprint("current_profile:", current_profile) 52 | gui.set_text(gui.get_node("status"), "fb_login - facebook.STATE_OPEN \nName:"..current_profile.name) 53 | end 54 | 55 | if data.status == facebook.STATE_CLOSED_LOGIN_FAILED then 56 | gui.set_text(gui.get_node("status"), "fb_login - facebook.STATE_CLOSED_LOGIN_FAILED") 57 | end 58 | 59 | if data.error then 60 | gui.set_text(gui.get_node("error"), "fb_login - " .. tostring(data.error)) 61 | else 62 | gui.set_text(gui.get_node("error"), "fb_login - no error") 63 | end 64 | end 65 | 66 | local function get_name_callback(self, id, response) 67 | gui.set_text(gui.get_node("status"), response.status) 68 | local r = json.decode(response.response) 69 | if r.error then 70 | gui.set_text(gui.get_node("error"), "get_name_callback - " .. tostring(r.error.message)) 71 | else 72 | gui.set_text(gui.get_node("error"), "get_name_callback - no error") 73 | end 74 | 75 | if r.name then 76 | gui.set_text(gui.get_node("name"), "get_name_callback - Hello " .. r.name) 77 | end 78 | end 79 | 80 | local function fb_share(self, result, error) 81 | pprint("fb_share - result table: ") 82 | pprint(result) 83 | if error then 84 | pprint("#################### fb_share, error: ") 85 | pprint(error) 86 | gui.set_text(gui.get_node("status"), "fb_share - ERROR") 87 | gui.set_text(gui.get_node("error"), "fb_share - " .. tostring(error.error)) 88 | else 89 | if result.post_id then 90 | gui.set_text(gui.get_node("status"), "fb_share - post id: " .. result.post_id) 91 | else 92 | gui.set_text(gui.get_node("status"), "fb_share - no post id in results") 93 | end 94 | gui.set_text(gui.get_node("error"), "fb_share - no error") 95 | end 96 | end 97 | 98 | local function deferred_deep_link(self, result, error) 99 | print("deferred_deep_link result: ") 100 | pprint(result) 101 | print("deferred_deep_link error: ") 102 | pprint(error) 103 | if error then 104 | pprint("#################### fb_deferredlinkapp, error: ") 105 | pprint(error) 106 | gui.set_text(gui.get_node("status"), "deferred_deep_link - ERROR") 107 | gui.set_text(gui.get_node("error"), "deferred_deep_link - " .. tostring(error.error)) 108 | else 109 | local str = "" 110 | for k, v in pairs(result) do 111 | str = str..k..":"..tostring(v)..", " 112 | end 113 | gui.set_text(gui.get_node("status"), "deferred_deep_link: " .. str) 114 | gui.set_text(gui.get_node("error"), "deferred_deep_link - no error") 115 | end 116 | end 117 | 118 | function on_input(self, action_id, action) 119 | if not action_id or not facebook then 120 | return 121 | end 122 | -- TODO add buttons for game requests 123 | 124 | -- https://developers.facebook.com/docs/facebook-login/permissions/#reference-default 125 | 126 | self.public_profile = dirtylarry:checkbox("checkbox_public_profile", action_id, action, self.public_profile) 127 | self.email = dirtylarry:checkbox("checkbox_email", action_id, action, self.email) 128 | self.publish_pages = dirtylarry:checkbox("checkbox_publish_pages", action_id, action, self.publish_pages) 129 | 130 | local permissions = {} 131 | if self.public_profile then 132 | table.insert(permissions, "public_profile") 133 | end 134 | if self.email then 135 | table.insert(permissions, "email") 136 | end 137 | if self.publish_pages then 138 | table.insert(permissions, "publish_pages") 139 | end 140 | 141 | dirtylarry:button("init", action_id, action, function () 142 | print("init") 143 | facebook.init() 144 | update_version(self) 145 | update_access_token(self) 146 | end) 147 | 148 | dirtylarry:button("gamereq_nonappusers", action_id, action, function () 149 | print("gamereq_nonappusers") 150 | facebook.show_dialog("apprequests", 151 | { 152 | title = "New Life!", 153 | data = "100017672412032", -- Billy Fiction 154 | message = "You got a life, congrats!", 155 | filters = facebook.GAMEREQUEST_FILTER_APPNONUSERS, 156 | }, 157 | fb_share) 158 | end) 159 | 160 | dirtylarry:button("gamereq_appusers", action_id, action, function () 161 | print("gamereq_appusers") 162 | facebook.show_dialog("apprequests", 163 | { 164 | title = "New Life!", 165 | data = "100014077812082", -- Defold Andersson 166 | message = "You got a life, congrats!", 167 | filters = facebook.GAMEREQUEST_FILTER_APPUSERS, 168 | }, 169 | fb_share) 170 | end) 171 | 172 | dirtylarry:button("gamereq_to", action_id, action, function () 173 | print("gamereq_to") 174 | facebook.show_dialog("apprequests", 175 | { 176 | title = "New life!", 177 | data = "100014077812082", -- Defold Andersson 178 | message = "You got a life! Good job!", 179 | to = "100017672412032,100014077812082" 180 | }, 181 | fb_share) 182 | end) 183 | dirtylarry:button("gamereq_recipients", action_id, action, function () 184 | print("gamereq_recipients") 185 | facebook.show_dialog("apprequests", 186 | { 187 | title = "New life!", 188 | data = "100014077812082", -- Defold Andersson 189 | message = "You got a life! Yay!", 190 | recipients = {"100017672412032","100014077812082"} 191 | }, 192 | fb_share) 193 | end) 194 | 195 | dirtylarry:button("login", action_id, action, function () 196 | print("login") 197 | facebook.login_with_permissions(permissions, facebook.AUDIENCE_EVERYONE, fb_login) 198 | end) 199 | 200 | dirtylarry:button("limited_login", action_id, action, function () 201 | print("limited_login") 202 | facebook.set_default_audience(facebook.AUDIENCE_EVERYONE) 203 | facebook.login_with_tracking_preference(facebook.LOGIN_TRACKING_LIMITED, permissions, "cryptononce", fb_login_limited) 204 | end) 205 | 206 | dirtylarry:button("share", action_id, action, function () 207 | print("share") 208 | local param = { link = "https://www.defold.com",picture="https://defold.com/images/logo/defold/logo/logo-ver-classic-white-160.png" } 209 | facebook.show_dialog("feed", param, fb_share) 210 | end) 211 | 212 | dirtylarry:button("logout", action_id, action, function () 213 | print("logout") 214 | facebook.logout() 215 | gui.set_text(gui.get_node("status"), "no status") 216 | gui.set_text(gui.get_node("error"), "no error") 217 | gui.set_text(gui.get_node("name"), "") 218 | update_access_token(self) 219 | end) 220 | 221 | dirtylarry:button("read_name", action_id, action, function () 222 | print("read_name") 223 | local token = facebook.access_token() 224 | if token then 225 | local url = "https://graph.facebook.com/me/?access_token=".. token 226 | http.request(url, "GET", get_name_callback) 227 | end 228 | end) 229 | 230 | dirtylarry:button("permissions", action_id, action, function () 231 | print("permissions") 232 | local permissions = facebook.permissions() 233 | local str = "Permissions:\n" 234 | for k,v in pairs(permissions) do 235 | str = str .. tostring(v) .. "\n" 236 | end 237 | gui.set_text(gui.get_node("status"), str) 238 | end) 239 | 240 | dirtylarry:button("deferredlinkapp", action_id, action, function () 241 | print("deferredlinkapp") 242 | facebook.deferred_deep_link(deferred_deep_link) 243 | end) 244 | 245 | dirtylarry:button("postevent", action_id, action, function () 246 | print("post event") 247 | local params = { [facebook.PARAM_LEVEL] = 23 } 248 | facebook.post_event(facebook.EVENT_ACHIEVED_LEVEL, 23, params) 249 | end) 250 | end 251 | 252 | function on_reload(self) 253 | -- Add input-handling code here 254 | -- Remove this function if not needed 255 | end 256 | -------------------------------------------------------------------------------- /main/gui.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/main/images/logo.png" 3 | } 4 | images { 5 | image: "/main/images/white-disc.png" 6 | } 7 | images { 8 | image: "/main/images/white-ring.png" 9 | } 10 | images { 11 | image: "/main/images/background.png" 12 | } 13 | images { 14 | image: "/main/images/white-logo.png" 15 | } 16 | margin: 0 17 | extrude_borders: 0 18 | inner_padding: 0 19 | -------------------------------------------------------------------------------- /main/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/main/images/background.png -------------------------------------------------------------------------------- /main/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/main/images/logo.png -------------------------------------------------------------------------------- /main/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/main/images/splash.png -------------------------------------------------------------------------------- /main/images/white-disc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/main/images/white-disc.png -------------------------------------------------------------------------------- /main/images/white-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/main/images/white-logo.png -------------------------------------------------------------------------------- /main/images/white-ring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defold/extension-facebook/3a01030c387e5d455630d2d2807d8efb2ce4837f/main/images/white-ring.png -------------------------------------------------------------------------------- /main/main.collection: -------------------------------------------------------------------------------- 1 | name: "main" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"facebook\"\n" 7 | " component: \"/main/facebook.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | --------------------------------------------------------------------------------