├── .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 | 
28 | 
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 | 
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 | 
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 | 
48 |
49 | 
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 | 
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 | 
72 |
73 | ## iOS
74 |
75 | For iOS you need to specify the game's `bundle_identifier` as specified in *game.project*.
76 |
77 | 
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 | 
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 | 
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 | 
96 |
97 | Select that you want to host a "HTML5 Bundle":
98 |
99 | 
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 | 
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 | 
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 | 
232 |
233 | Events are accessed from the *Events* option on the from the Facebook Analytics page:
234 |
235 | 
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 |
--------------------------------------------------------------------------------