diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..ee9a439 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: defold +patreon: Defold +custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NBNBHTUW4GS4C'] diff --git a/.github/workflows/trigger-site-rebuild.yml b/.github/workflows/trigger-site-rebuild.yml new file mode 100644 index 0000000..ec697cc --- /dev/null +++ b/.github/workflows/trigger-site-rebuild.yml @@ -0,0 +1,19 @@ +name: Trigger site rebuild + +on: [push] + +jobs: + site-rebuild: + runs-on: ubuntu-latest + + steps: [ + { + name: 'Repository dispatch', + uses: defold/repository-dispatch@1.2.1, + with: { + repo: 'defold/defold.github.io', + token: '${{ secrets.SERVICES_GITHUB_TOKEN }}', + user: 'services@defold.se', + action: 'extension-siwa' + } + }] diff --git a/README.md b/README.md index b1c99d8..035529b 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -# Sign in with Apple -Native extension to support the Sign in with Apple feature in Defold. +# Sign in with Apple extension for Defold -"Sign in with Apple makes it easy for users to sign in to your apps and websites using their Apple ID. Instead of filling out forms, verifying email addresses, and choosing new passwords, they can use Sign in with Apple to set up an account and start using your app right away. All accounts are protected with two-factor authentication for superior security, and Apple will not track users’ activity in your app or website" [Apple Developer docs](https://developer.apple.com/sign-in-with-apple/) +Defold [native extension](https://www.defold.com/manuals/extensions/) which provides access to Sign in with Apple functionality on iOS. -"Apps that use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option. A user’s primary account is the account they establish with your app for the purposes of identifying themselves, signing in, and accessing your features and associated services." [Apple review guidelines](https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple) +[Manual, API and setup instructions](https://www.defold.com/extension-siwa/) is available on the official Defold site. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..eea7daa --- /dev/null +++ b/docs/index.md @@ -0,0 +1,29 @@ +--- +title: Sign in with Apple extension for Defold +brief: This manual covers how to setup and use Sign in with Apple in Defold. +--- + +# Sign in with Apple extension for Defold + +This extension provides functions to use [Sign in with Apple](https://developer.apple.com/sign-in-with-apple/) to allow users to set up an account and sign in to your game with their Apple ID. + +## Installation + +To use this library in your Defold project, add the following URL to your `game.project` dependencies: + +https://github.com/defold/extension-siwa/archive/master.zip + +We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-siwa/releases). + + +## Setting up your app for Sign in with Apple + +To get started you need to enable your app’s App ID with the Sign in with Apple capability. [Follow the official Apple developer instructions](https://help.apple.com/developer-account/?lang=en#/devde676e696) to get started. + + +## Source code + +The source code is available on [GitHub](https://github.com/defold/extension-siwa) + + +## API reference diff --git a/game.project b/game.project index 84a1974..fe0d1d3 100755 --- a/game.project +++ b/game.project @@ -13,8 +13,8 @@ main_collection = /main/main.collectionc [display] width = 640 -height = 480 +height = 1136 [ios] -bundle_identifier = com.defold.siwa +bundle_identifier = com.defold.extension.siwa diff --git a/main/main.gui b/main/main.gui index 33288b6..6aa1014 100755 --- a/main/main.gui +++ b/main/main.gui @@ -9,123 +9,6 @@ background_color { z: 0.0 w: 0.0 } -nodes { - position { - x: 320.0 - y: 239.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 640.0 - y: 480.0 - z: 0.0 - w: 1.0 - } - color { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "" - id: "box" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: false - size_mode: SIZE_MODE_MANUAL -} -nodes { - position { - x: 3.0 - y: 2.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Hello World, I\'m SIWA" - font: "system_font" - id: "test_text" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "box" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - template_node_child: false - text_leading: 1.0 - text_tracking: 0.0 -} nodes { position { x: 114.0 @@ -601,6 +484,68 @@ nodes { text_leading: 1.0 text_tracking: 0.0 } +nodes { + position { + x: 20.0 + y: 899.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 600.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Hello World, I\'m SIWA" + font: "system_font" + id: "test_text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_NW + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: true + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 1.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} material: "/builtins/materials/gui.material" adjust_reference: ADJUST_REFERENCE_PARENT max_nodes: 512 diff --git a/main/main.gui_script b/main/main.gui_script index 1a660d9..6480b6b 100755 --- a/main/main.gui_script +++ b/main/main.gui_script @@ -1,21 +1,20 @@ local dl = require("dirtylarry.dirtylarry") -local good_id = "001869.37272b1d2d6e454d8b396e521ba4fc12.1006" -local bad_id = "abc123" - +local lines = {} local function log(msg, ...) msg = msg:format(...) print(msg) - gui.set_text(gui.get_node("test_text"), msg) + + table.insert(lines, 1, msg) + table.remove(lines, 10) + gui.set_text(gui.get_node("test_text"), table.concat(lines, "\n")) end -local function check_credentials_status(self, id) - log("check_credentials_status %s", id) - siwa.check_credentials_status(id, function(self, data) - log("result: %s", data.result) - for k, v in pairs(data) do - pprint(k) - pprint(v) +local function get_credential_state(self, id) + log("get_credential_state %s", id) + siwa.get_credential_state(id, function(self, data) + for k,v in pairs(data) do + log("%s: %s", k, tostring(v)) end end) end @@ -23,16 +22,15 @@ end local function authenticate(self) log("authenticate") siwa.authenticate(function(self, data) - log("result: %s", data.result) - for k, v in pairs(data) do - print(k) - pprint(v) + self.user_id = data.user_id + for k,v in pairs(data) do + log("%s: %s", k, tostring(v)) end end) end local function is_siwa_supported() - return siwa and siwa.is_siwa_supported() + return siwa and siwa.is_supported() end function init(self) @@ -48,11 +46,15 @@ end function on_input(self, action_id, action) if is_siwa_supported() then dl:button("check", action_id, action, function() - check_credentials_status(self, good_id) + if self.user_id then + get_credential_state(self, self.user_id) + else + log("No user id. Login first") + end end) dl:button("check_fail", action_id, action, function() - check_credentials_status(self, bad_id) + check_credentials_status(self, "foobar") end) dl:button("login", action_id, action, function() diff --git a/siwa/api/siwa.script_api b/siwa/api/siwa.script_api new file mode 100644 index 0000000..08c7a4d --- /dev/null +++ b/siwa/api/siwa.script_api @@ -0,0 +1,90 @@ +- name: siwa + type: table + desc: Functions and constants for interacting Sign in with Apple. + [icon:ios] + members: + +#***************************************************************************************************** + + - name: is_supported + type: function + desc: Check if Sign in with Apple is available (iOS 13+). + + +#***************************************************************************************************** + + - name: get_credential_state + type: function + desc: Get the credential state of a user. + + parameters: + - name: user_id + type: string + desc: User id to get credential state for. + - name: callback + type: function + desc: Credential state callback function. + parameters: + - name: self + type: object + desc: The current object. + + - name: state + type: table + desc: The credential state (fields: user_id, credential_state) + +#***************************************************************************************************** + + - name: authenticate + type: function + desc: Show the Sign in with Apple UI + + parameters: + - name: callback + type: function + desc: Authentication callback function. + parameters: + - name: self + type: object + desc: The current object. + + - name: state + type: table + desc: The authentication result data (fields: user_id, identity_token, email, first_name, family_name, status, result) + + +#***************************************************************************************************** + + - name: STATE_NOT_FOUND + type: number + desc: The user can’t be found. + + + - name: STATE_UNKNOWN + type: number + desc: Unknown credential state. + + + - name: STATE_AUTHORIZED + type: number + desc: The user is authorized. + + + - name: STATE_REVOKED + type: number + desc: Authorization for the given user has been revoked. + + + - name: STATUS_UNKNOWN + type: number + desc: The system hasn’t determined whether the user might be a real person. + + + - name: STATUS_UNSUPPORTED + type: number + desc: The system can’t determine this user’s status as a real person. + + + - name: STATUS_LIKELY_REAL + type: number + desc: The user appears to be a real person. diff --git a/siwa/src/siwa.cpp b/siwa/src/siwa.cpp new file mode 100755 index 0000000..882da87 --- /dev/null +++ b/siwa/src/siwa.cpp @@ -0,0 +1,292 @@ +#if defined(DM_PLATFORM_IOS) + +#include "siwa.h" +#include + +#define MODULE_NAME "siwa" + +SiwaData g_SiwaData; +SiwaCallbackData g_SiwaCallbackData; + +char* Siwa_GetUserId() +{ + return g_SiwaData.m_userID; +} + +static void Siwa_ResetCallbackData() +{ + free(g_SiwaCallbackData.m_userID); + g_SiwaCallbackData.m_userID = 0; + free(g_SiwaCallbackData.m_identityToken); + g_SiwaCallbackData.m_identityToken = 0; + free(g_SiwaCallbackData.m_userID); + g_SiwaCallbackData.m_userID = 0; + free(g_SiwaCallbackData.m_email); + g_SiwaCallbackData.m_email = 0; + free(g_SiwaCallbackData.m_firstName); + g_SiwaCallbackData.m_firstName = 0; + free(g_SiwaCallbackData.m_familyName); + g_SiwaCallbackData.m_familyName = 0; + free(g_SiwaCallbackData.m_identityToken); + g_SiwaCallbackData.m_identityToken = 0; + free(g_SiwaCallbackData.m_message); + g_SiwaCallbackData.m_message = 0; + + g_SiwaCallbackData.m_userStatus = STATUS_UNSUPPORTED; + g_SiwaCallbackData.m_state = STATE_UNKNOWN; + g_SiwaCallbackData.m_cmd = CMD_NONE; +} + +void Siwa_QueueCredentialCallback(const char* userID, const SiwaCredentialState state) +{ + if(g_SiwaCallbackData.m_cmd != CMD_NONE) { + dmLogError("Can't queue credential callback, already have a callback queued!"); + return; + } + + g_SiwaCallbackData.m_cmd = CMD_CREDENTIAL; + g_SiwaCallbackData.m_userID = strdup(userID); + g_SiwaCallbackData.m_state = state; +} + + +void Siwa_QueueAuthSuccessCallback(const char* identityToken, const char* userID, const char* email, const char* firstName, const char* familyName, const SiwaUserDetectionStatus userStatus) +{ + if(g_SiwaCallbackData.m_cmd != CMD_NONE) { + dmLogError("Can't queue auth success callback, already have a callback queued!"); + return; + } + + g_SiwaCallbackData.m_cmd = CMD_AUTH_SUCCESS; + g_SiwaCallbackData.m_identityToken = strdup(identityToken); + g_SiwaCallbackData.m_userID = strdup(userID); + g_SiwaCallbackData.m_email = strdup(email != 0 ? email: ""); + g_SiwaCallbackData.m_firstName = strdup(firstName != 0 ? firstName: ""); + g_SiwaCallbackData.m_familyName = strdup(familyName != 0 ? familyName : ""); + g_SiwaCallbackData.m_userStatus = userStatus; + g_SiwaCallbackData.m_message = strdup(""); +} + +void Siwa_QueueAuthFailureCallback(const char* message) +{ + if(g_SiwaCallbackData.m_cmd != CMD_NONE) { + dmLogError("Can't queue auth error callback, already have a callback queued!"); + return; + } + + g_SiwaCallbackData.m_cmd = CMD_AUTH_FAILED; + g_SiwaCallbackData.m_message = strdup(message); +} + + +static void Siwa_TriggerCallback() +{ + lua_State* L = dmScript::GetCallbackLuaContext(g_SiwaData.m_callback); + DM_LUA_STACK_CHECK(L, 0); + + if (dmScript::SetupCallback(g_SiwaData.m_callback)) + { + lua_createtable(L, 0, 3); + + if (g_SiwaCallbackData.m_cmd == CMD_CREDENTIAL) + { + lua_pushstring(L, "result"); + lua_pushstring(L, "SUCCESS"); + lua_settable(L, -3); + + lua_pushstring(L, "user_id"); + lua_pushstring(L, g_SiwaCallbackData.m_userID); + lua_settable(L, -3); + + lua_pushstring(L, "credential_state"); + lua_pushnumber(L, g_SiwaCallbackData.m_state); + lua_settable(L, -3); + } + else if (g_SiwaCallbackData.m_cmd == CMD_AUTH_SUCCESS) + { + lua_pushstring(L, "result"); + lua_pushstring(L, "SUCCESS"); + lua_settable(L, -3); + + lua_pushstring(L, "identity_token"); + lua_pushstring(L, g_SiwaCallbackData.m_identityToken); + lua_settable(L, -3); + + lua_pushstring(L, "user_id"); + lua_pushstring(L, g_SiwaCallbackData.m_userID); + lua_settable(L, -3); + + lua_pushstring(L, "email"); + lua_pushstring(L, g_SiwaCallbackData.m_email); + lua_settable(L, -3); + + lua_pushstring(L, "first_name"); + lua_pushstring(L, g_SiwaCallbackData.m_firstName); + lua_settable(L, -3); + + lua_pushstring(L, "family_name"); + lua_pushstring(L, g_SiwaCallbackData.m_familyName); + lua_settable(L, -3); + + lua_pushstring(L, "user_status"); + lua_pushnumber(L, g_SiwaCallbackData.m_userStatus); + lua_settable(L, -3); + } + else if (g_SiwaCallbackData.m_cmd == CMD_AUTH_FAILED) + { + lua_pushstring(L, "result"); + lua_pushstring(L, "ERROR"); + lua_settable(L, -3); + + lua_pushstring(L, "message"); + lua_pushstring(L, g_SiwaCallbackData.m_message); + lua_settable(L, -3); + } + + if (lua_pcall(L, 2, 0, 0) != 0) + { + dmLogError("Error running siwa callback: %s", lua_tostring(L, -1)); + lua_pop(L, 1); + } + dmScript::TeardownCallback(g_SiwaData.m_callback); + } +} + + +static void Siwa_SetupCallback(lua_State* L, int index) +{ + if (g_SiwaData.m_callback) { + dmScript::DestroyCallback(g_SiwaData.m_callback); + } + g_SiwaData.m_callback = dmScript::CreateCallback(L, index); +} + +static void Siwa_CleanupCallback() { + if (g_SiwaData.m_callback) { + dmScript::DestroyCallback(g_SiwaData.m_callback); + g_SiwaData.m_callback = 0; + } +} + +static int Siwa_GetCredentialState(lua_State* L){ + DM_LUA_STACK_CHECK(L, 1); + + if (!Siwa_PlatformIsSupported()) { + dmLogWarning("Sign in with Apple is not available"); + lua_pushboolean(L, 0); + return 1; + } + + if(g_SiwaData.m_callback != 0) + { + dmLogError("Callback already in progress"); + lua_pushboolean(L, 0); + return 1; + } + + luaL_checktype(L, 1, LUA_TSTRING); + if (g_SiwaData.m_userID) free(g_SiwaData.m_userID); + g_SiwaData.m_userID = strdup(lua_tostring(L, 1)); + + luaL_checktype(L, 2, LUA_TFUNCTION); + Siwa_SetupCallback(L, 2); + Siwa_PlatformGetCredentialState(); + + lua_pushboolean(L, 1); + return 1; +} + +static int Siwa_AuthenticateWithApple(lua_State* L) { + DM_LUA_STACK_CHECK(L, 1); + + if (!Siwa_PlatformIsSupported()) { + dmLogWarning("Sign in with Apple is not available"); + lua_pushboolean(L, 0); + return 1; + } + + if(g_SiwaData.m_callback != 0) + { + dmLogError("Callback already in progress"); + lua_pushboolean(L, 0); + return 1; + } + + luaL_checktype(L, 1, LUA_TFUNCTION); + Siwa_SetupCallback(L, 1); + Siwa_PlatformAuthenticateWithApple(); + + lua_pushboolean(L, 1); + return 1; +} + +static int Siwa_IsSupported(lua_State* L) { + DM_LUA_STACK_CHECK(L, 1); + lua_pushboolean(L, Siwa_PlatformIsSupported()); + return 1; +} + +static dmExtension::Result SiwaAppInitialize(dmExtension::AppParams* params) +{ + Siwa_ResetCallbackData(); + return dmExtension::RESULT_OK; +} + +static dmExtension::Result SiwaAppFinalize(dmExtension::AppParams* params) +{ + return dmExtension::RESULT_OK; +} + +const luaL_reg lua_register[] = +{ + {"is_supported", Siwa_IsSupported}, + {"get_credential_state", Siwa_GetCredentialState}, + {"authenticate", Siwa_AuthenticateWithApple}, + {0, 0} +}; + +static dmExtension::Result SiwaInitialize(dmExtension::Params* params) +{ + lua_State* L = params->m_L; + int top = lua_gettop(L); + luaL_register(L, MODULE_NAME, lua_register); + + #define SETCONSTANT(name) \ + lua_pushnumber(L, (lua_Number) name); \ + lua_setfield(L, -2, #name);\ + + SETCONSTANT(STATE_NOT_FOUND) + SETCONSTANT(STATE_UNKNOWN) + SETCONSTANT(STATE_AUTHORIZED) + SETCONSTANT(STATE_REVOKED) + + SETCONSTANT(STATUS_UNKNOWN) + SETCONSTANT(STATUS_UNSUPPORTED) + SETCONSTANT(STATUS_LIKELY_REAL) + + #undef SETCONSTANT + + lua_pop(L, 1); + assert(top == lua_gettop(L)); + return dmExtension::RESULT_OK; +} + +static dmExtension::Result SiwaUpdate(dmExtension::Params* params) +{ + if(g_SiwaCallbackData.m_cmd != CMD_NONE) + { + Siwa_TriggerCallback(); + Siwa_ResetCallbackData(); + Siwa_CleanupCallback(); + } + return dmExtension::RESULT_OK; +} + +static dmExtension::Result SiwaFinalize(dmExtension::Params* params) +{ + return dmExtension::RESULT_OK; +} + +DM_DECLARE_EXTENSION(siwa, MODULE_NAME, SiwaAppInitialize, SiwaAppFinalize, SiwaInitialize, SiwaUpdate, 0, SiwaFinalize); + +#endif diff --git a/siwa/src/siwa.h b/siwa/src/siwa.h new file mode 100755 index 0000000..767c029 --- /dev/null +++ b/siwa/src/siwa.h @@ -0,0 +1,86 @@ +#pragma once +#if defined(DM_PLATFORM_IOS) + +#include + +enum SiwaCallbackCmd +{ + CMD_NONE = 0, + CMD_CREDENTIAL = 1, + CMD_AUTH_SUCCESS = 2, + CMD_AUTH_FAILED = 3 +}; + +// https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovidercredentialstate/asauthorizationappleidprovidercredentialauthorized?language=objc +enum SiwaCredentialState +{ + STATE_UNKNOWN = 0, + STATE_AUTHORIZED = 1, + STATE_REVOKED = 2, + STATE_NOT_FOUND = 3 +}; + +// https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus?language=objc +enum SiwaUserDetectionStatus +{ + STATUS_UNSUPPORTED = 0, + STATUS_LIKELY_REAL = 1, + STATUS_UNKNOWN = 2 +}; + +struct SiwaCallbackData +{ + SiwaCallbackData() + { + memset(this, 0, sizeof(*this)); + }; + + SiwaCallbackCmd m_cmd; + SiwaCredentialState m_state; + SiwaUserDetectionStatus m_userStatus; + + char* m_identityToken; + char* m_userID; + char* m_email; + char* m_firstName; + char* m_familyName; + + char* m_message; +}; + +struct SiwaData +{ + SiwaData() + { + memset(this, 0, sizeof(*this)); + }; + + // the user ID used for checking credential state + char* m_userID; + + dmScript::LuaCallbackInfo* m_callback; +}; + +char* Siwa_GetUserId(); + +// Queue the credential check callback to be triggered next update call in the main thread. +void Siwa_QueueCredentialCallback(const char* userID, const SiwaCredentialState state); +// Queue the sign in authorization callback to be triggered next update call in the main thread, when authorization succeeds. +void Siwa_QueueAuthSuccessCallback(const char* identityToken, const char* userID, const char* email, const char* firstName, const char* familyName, const SiwaUserDetectionStatus userStatus); +// Queue the sign in authorization callback to be triggered next update call in the main thread, when authorization fails. +void Siwa_QueueAuthFailureCallback(const char* message); + +// Trigged by a call from lua to check if sign in with apple is supported on this device. +bool Siwa_PlatformIsSupported(); + +// Triggered by a call from lua to start the sign in with apple flow +// expects the callback to be a reference number to the lua registry +// expects the context to be reference number to the lua registry +void Siwa_PlatformAuthenticateWithApple(); + +// Triggered by a call from lua to check if a provided apple id grants this app permission to use that id. +// expects the callback to be a reference number to the lua registry +// expects the context to be reference number to the lua registry +void Siwa_PlatformGetCredentialState(); + +#endif diff --git a/siwa/src/siwa_callbacks.cpp b/siwa/src/siwa_callbacks.cpp deleted file mode 100755 index 094e100..0000000 --- a/siwa/src/siwa_callbacks.cpp +++ /dev/null @@ -1,531 +0,0 @@ -#include "siwa_callbacks.h" - -#if defined(DM_PLATFORM_IOS) - -#include -#include -#include - -#include -#include - -namespace dmSiwa { - - // access to these structs should be protected by mutex - // as they are used by callbacks that can trigger in arbitrary contexts. - static CredentialCallbackData g_CredentialCallbackData; - static AuthCallbackData g_AuthCallbackData; - - static dmMutex::HMutex g_CredentialCallbackMutex; - static dmMutex::HMutex g_AuthCallbackMutex; -} - -void dmSiwa::InitCallbacks() -{ - InitCredentialCallbackData(); - InitAuthCallbackData(); -} - -void dmSiwa::CheckForQueuedCallbacks() -{ - CheckForCredentialCallback(); - CheckForAuthCallback(); -} - -// do the common setup for lua callbacks for this extension -bool dmSiwa::RunCallbackSetup(lua_State* L, int _self, int _callback) -{ - if (_callback != LUA_NOREF) - { - lua_rawgeti(L, LUA_REGISTRYINDEX, _callback); - lua_rawgeti(L, LUA_REGISTRYINDEX, _self); - lua_pushvalue(L, -1); - dmScript::SetInstance(L); - return true; - } - else - { - dmLogError("No callback set for siwa"); - return false; - } -} - -//================================================================== -// Callback for checking the credential state of a provided sign in with Apple ID -//================================================================== -void dmSiwa::InitCredentialCallbackData() -{ - g_CredentialCallbackMutex = dmMutex::New(); - ResetCredentialCallbackData(); -} - -void dmSiwa::ResetCredentialCallbackData() -{ - DM_MUTEX_SCOPED_LOCK(g_CredentialCallbackMutex) - g_CredentialCallbackData.m_L = nullptr; - g_CredentialCallbackData.m_self = LUA_NOREF; - g_CredentialCallbackData.m_callback = LUA_NOREF; - g_CredentialCallbackData.m_userID = nullptr; - g_CredentialCallbackData.m_status = -1; - - g_CredentialCallbackData.m_cbState = IDLE; -} - -void dmSiwa::QueueCredentialCallback(lua_State* L, int _self, int _callback, char* _userID, int status) -{ - DM_MUTEX_SCOPED_LOCK(g_CredentialCallbackMutex) - if(g_CredentialCallbackData.m_cbState != IDLE){ - dmLogError("Can't queue credential callback, already queued or running!"); - return; - } - - g_CredentialCallbackData.m_L = L; - g_CredentialCallbackData.m_self = _self; - g_CredentialCallbackData.m_callback = _callback; - g_CredentialCallbackData.m_userID = strdup(_userID); - g_CredentialCallbackData.m_status = status; - - g_CredentialCallbackData.m_cbState = QUEUED; -} - -void dmSiwa::RunCredentialCallback() -{ - lua_State* L = nullptr; - int _self = LUA_NOREF; - int _callback = LUA_NOREF; - char* _userID = nullptr; - int status = -1; - - { - DM_MUTEX_SCOPED_LOCK(g_CredentialCallbackMutex) - - L = g_CredentialCallbackData.m_L; - - _self = g_CredentialCallbackData.m_self; - g_CredentialCallbackData.m_self = LUA_NOREF; - - _callback = g_CredentialCallbackData.m_callback; - g_CredentialCallbackData.m_callback = LUA_NOREF; - - if(g_CredentialCallbackData.m_userID != nullptr) - { - _userID = strdup(g_CredentialCallbackData.m_userID); - free(g_CredentialCallbackData.m_userID); - g_CredentialCallbackData.m_userID = nullptr; - } - - status = g_CredentialCallbackData.m_status; - } - - // if the pre setup fails, bail - if(L == nullptr or _self == LUA_NOREF or _callback == LUA_NOREF or _userID == nullptr - or !RunCallbackSetup(L, _self, _callback)) - { - dmLogError("Could not run siwa credential callback because a parameter was null or missing"); - lua_pop(L, 2); - return; - } - - if (dmScript::IsInstanceValid(L)) - { - lua_createtable(L, 0, 3); - - lua_pushstring(L, "result"); - lua_pushstring(L, "SUCCESS"); - lua_settable(L, -3); - - lua_pushstring(L, "user_id"); - lua_pushstring(L, _userID); - lua_settable(L, -3); - - lua_pushstring(L, "credential_status"); - lua_pushnumber(L, status); - lua_settable(L, -3); - - if (lua_pcall(L, 2, 0, 0) != 0) - { - dmLogError("Error running siwa credential callback: %s", lua_tostring(L, -1)); - lua_pop(L, 1); - } - - free(_userID); - dmScript::Unref(L, LUA_REGISTRYINDEX, _callback); - dmScript::Unref(L, LUA_REGISTRYINDEX, _self); - } - else - { - dmLogError("Could not run siwa credential callback because the instance is invalid."); - lua_pop(L, 2); - } -} - -void dmSiwa::CheckForCredentialCallback() -{ - bool triggerCallback = false; - { - DM_MUTEX_SCOPED_LOCK(g_CredentialCallbackMutex) - if(g_CredentialCallbackData.m_cbState == QUEUED) - { - triggerCallback = true; - g_CredentialCallbackData.m_cbState = RUNNING; - } - } - - if(triggerCallback) - { - RunCredentialCallback(); - ResetCredentialCallbackData(); - } -} - -//================================================================== -// Callback for the sign in with Apple ID -//================================================================== - -void dmSiwa::InitAuthCallbackData() -{ - g_AuthCallbackMutex = dmMutex::New(); - ResetAuthCallbackData(); -} - -void dmSiwa::ResetAuthCallbackData() -{ - DM_MUTEX_SCOPED_LOCK(g_AuthCallbackMutex) - g_AuthCallbackData.m_L = nullptr; - g_AuthCallbackData.m_self = LUA_NOREF; - g_AuthCallbackData.m_callback = LUA_NOREF; - - g_AuthCallbackData.m_identityToken = nullptr; - g_AuthCallbackData.m_userID = nullptr; - g_AuthCallbackData.m_email = nullptr; - g_AuthCallbackData.m_firstName = nullptr; - g_AuthCallbackData.m_familyName = nullptr; - - g_AuthCallbackData.m_userStatus = -1; - - g_AuthCallbackData.m_message = nullptr; - - g_AuthCallbackData.m_cbState = IDLE; - g_AuthCallbackData.m_success = false; -} - -void dmSiwa::QueueAuthSuccessCallback(lua_State* L, int _self, int _callback, const char* identityToken, const char* userID, const char* email, const char* firstName, const char* familyName, int userStatus) -{ - DM_MUTEX_SCOPED_LOCK(g_AuthCallbackMutex) - if(g_AuthCallbackData.m_cbState != IDLE){ - dmLogError("Can't queue auth success callback, already queued or running!"); - return; - } - - g_AuthCallbackData.m_L = L; - g_AuthCallbackData.m_self = _self; - g_AuthCallbackData.m_callback = _callback; - - if(identityToken != nullptr) - { - g_AuthCallbackData.m_identityToken = strdup(identityToken); - } - - if(userID != nullptr) - { - g_AuthCallbackData.m_userID = strdup(userID); - } - - if(email != nullptr) - { - g_AuthCallbackData.m_email = strdup(email); - } - - if(firstName != nullptr) - { - g_AuthCallbackData.m_firstName = strdup(firstName); - } - - if(familyName != nullptr) - { - g_AuthCallbackData.m_familyName = strdup(familyName); - } - - g_AuthCallbackData.m_userStatus = userStatus; - - g_AuthCallbackData.m_message = nullptr; - - g_AuthCallbackData.m_cbState = QUEUED; - g_AuthCallbackData.m_success = true; -} - -void dmSiwa::QueueAuthFailureCallback(lua_State* L, int _self, int _callback, const char* message) -{ - DM_MUTEX_SCOPED_LOCK(g_AuthCallbackMutex) - if(g_AuthCallbackData.m_cbState != IDLE){ - dmLogError("Can't queue auth error callback, already queued or running!"); - return; - } - - g_AuthCallbackData.m_L = L; - g_AuthCallbackData.m_self = _self; - g_AuthCallbackData.m_callback = _callback; - - g_AuthCallbackData.m_message = strdup(message); - - g_AuthCallbackData.m_cbState = QUEUED; - g_AuthCallbackData.m_success = false; -} - -void dmSiwa::CheckForAuthCallback() -{ - bool triggerCallback = false; - bool success = false; - { - DM_MUTEX_SCOPED_LOCK(g_AuthCallbackMutex) - if(g_AuthCallbackData.m_cbState == QUEUED) - { - triggerCallback = true; - success = g_AuthCallbackData.m_success; - g_AuthCallbackData.m_cbState = RUNNING; - } - } - - if(triggerCallback) - { - if(success) - { - RunAuthSuccessCallback(); - } - else - { - RunAuthFailureCallback(); - } - - ResetAuthCallbackData(); - } -} - -void dmSiwa::RunAuthSuccessCallback() -{ - lua_State* L = nullptr; - int _self = LUA_NOREF; - int _callback = LUA_NOREF; - - char* identityToken = nullptr; - char* userID = nullptr; - char* email = nullptr; - char* firstName = nullptr; - char* familyName = nullptr; - int userStatus = -1; - { - DM_MUTEX_SCOPED_LOCK(g_AuthCallbackMutex); - L = g_AuthCallbackData.m_L; - userStatus = g_AuthCallbackData.m_userStatus; - g_AuthCallbackData.m_L = nullptr; - - // take ownership of auth data - _self = g_AuthCallbackData.m_self; - g_AuthCallbackData.m_self = LUA_NOREF; - - _callback = g_AuthCallbackData.m_callback; - g_AuthCallbackData.m_callback = LUA_NOREF; - - if(g_AuthCallbackData.m_identityToken != nullptr) - { - identityToken = strdup(g_AuthCallbackData.m_identityToken); - free(g_AuthCallbackData.m_identityToken); - g_AuthCallbackData.m_identityToken = nullptr; - } - - if(g_AuthCallbackData.m_userID != nullptr) - { - userID = strdup(g_AuthCallbackData.m_userID); - free(g_AuthCallbackData.m_userID); - g_AuthCallbackData.m_userID = nullptr; - } - - if(g_AuthCallbackData.m_email != nullptr) - { - email = strdup(g_AuthCallbackData.m_email); - free(g_AuthCallbackData.m_email); - g_AuthCallbackData.m_email = nullptr; - } - - if(g_AuthCallbackData.m_firstName != nullptr) - { - firstName = strdup(g_AuthCallbackData.m_firstName); - free(g_AuthCallbackData.m_firstName); - g_AuthCallbackData.m_firstName = nullptr; - } - - if(g_AuthCallbackData.m_familyName != nullptr) - { - familyName = strdup(g_AuthCallbackData.m_familyName); - free(g_AuthCallbackData.m_familyName); - g_AuthCallbackData.m_familyName = nullptr; - } - } - - // if the pre setup fails, bail - // first name, family name, and email can be null in normal execution, as they are not provided - // after a user's first login to our app. Userid and identity token should always be provided. - if(L == nullptr or _self == LUA_NOREF or _callback == LUA_NOREF or identityToken == nullptr or userID == nullptr - or !RunCallbackSetup(L, _self, _callback)) - { - dmLogError("Could not run siwa auth success callback because a parameter was null or missing"); - - free(identityToken); - free(userID); - free(email); - free(firstName); - free(familyName); - - if(_self != LUA_NOREF) - { - dmScript::Unref(L, LUA_REGISTRYINDEX, _self); - } - - if(_callback != LUA_NOREF) - { - dmScript::Unref(L, LUA_REGISTRYINDEX, _callback); - } - - lua_pop(L, 2); - return; - } - - if (dmScript::IsInstanceValid(L)) - { - lua_createtable(L, 0, 7); - - lua_pushstring(L, "result"); - lua_pushstring(L, "SUCCESS"); - lua_settable(L, -3); - - lua_pushstring(L, "identity_token"); - lua_pushstring(L, identityToken); - lua_settable(L, -3); - - lua_pushstring(L, "user_id"); - lua_pushstring(L, userID); - lua_settable(L, -3); - - lua_pushstring(L, "email"); - lua_pushstring(L, email); - lua_settable(L, -3); - - lua_pushstring(L, "first_name"); - lua_pushstring(L, firstName); - lua_settable(L, -3); - - lua_pushstring(L, "family_name"); - lua_pushstring(L, familyName); - lua_settable(L, -3); - - lua_pushstring(L, "user_status"); - lua_pushnumber(L, userStatus); - lua_settable(L, -3); - - if (lua_pcall(L, 2, 0, 0) != 0) - { - dmLogError("Error running siwa auth success callback: %s", lua_tostring(L, -1)); - lua_pop(L, 1); - } - - // clean up owned data - free(identityToken); - free(userID); - free(email); - free(firstName); - free(familyName); - - dmScript::Unref(L, LUA_REGISTRYINDEX, _callback); - dmScript::Unref(L, LUA_REGISTRYINDEX, _self); - } - else - { - dmLogError("Could not run siwa auth success callback because the instance is invalid."); - lua_pop(L, 2); - } -} - -void dmSiwa::RunAuthFailureCallback() -{ - lua_State* L = nullptr; - int _self = LUA_NOREF; - int _callback = LUA_NOREF; - - char* message = nullptr; - { - DM_MUTEX_SCOPED_LOCK(g_AuthCallbackMutex) - L = g_AuthCallbackData.m_L; - - // take ownership of authorization data - _self = g_AuthCallbackData.m_self; - g_AuthCallbackData.m_self = LUA_NOREF; - - _callback = g_AuthCallbackData.m_callback; - g_AuthCallbackData.m_callback = LUA_NOREF; - - if(g_AuthCallbackData.m_message != nullptr) - { - message = strdup(g_AuthCallbackData.m_message); - free(g_AuthCallbackData.m_message); - g_AuthCallbackData.m_message = nullptr; - } - } - - // if the pre setup fails, bail - if(L == nullptr or _self == LUA_NOREF or _callback == LUA_NOREF or message == nullptr - or !RunCallbackSetup(L, _self, _callback)) - { - dmLogError("Could not run siwa auth error callback because a parameter was null or missing"); - - free(message); - - if(_self != LUA_NOREF) - { - dmScript::Unref(L, LUA_REGISTRYINDEX, _self); - } - - if(_callback != LUA_NOREF) - { - dmScript::Unref(L, LUA_REGISTRYINDEX, _callback); - } - - lua_pop(L, 2); - return; - } - - if (dmScript::IsInstanceValid(L)) - { - lua_createtable(L, 0, 2); - - lua_pushstring(L, "result"); - lua_pushstring(L, "ERROR"); - lua_settable(L, -3); - - lua_pushstring(L, "message"); - lua_pushstring(L, message); - lua_settable(L, -3); - - if (lua_pcall(L, 2, 0, 0) != 0) - { - dmLogError("Error running siwa auth error callback: %s", lua_tostring(L, -1)); - lua_pop(L, 1); - } - - free(message); - dmScript::Unref(L, LUA_REGISTRYINDEX, _callback); - dmScript::Unref(L, LUA_REGISTRYINDEX, _self); - } - else - { - dmLogError("Could not run siwa auth error callback because the instance is invalid."); - - free(message); - dmScript::Unref(L, LUA_REGISTRYINDEX, _callback); - dmScript::Unref(L, LUA_REGISTRYINDEX, _self); - - // We need to clean up our stack, which has 2 elements at this point (see comment 4:) - lua_pop(L, 2); - } -} - -#endif diff --git a/siwa/src/siwa_callbacks.h b/siwa/src/siwa_callbacks.h deleted file mode 100755 index d8c5bf1..0000000 --- a/siwa/src/siwa_callbacks.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -// A collection of helper structs and functions for running lua callbacks provided -// when the module methods were initially called. The sign in with apple sign in flow -// and credential state checking - -struct lua_State; - -namespace dmSiwa { - - // The state of our own callbacks: - // IDLE: default state, available to trigger a callbak - // QUEUED: We've received a callback from able, and have queued our own associated callback - // to run on the main thread. - // RUNNING: Our callback is currently running on the main thread. - enum CallbackState{ - IDLE, - QUEUED, - RUNNING - }; - - // Holds data recevied from the callback for checking credential state - // so that we can access it on the main thread, since the Apple callback - // can be triggered in any arbitrary thread context. - struct CredentialCallbackData{ - public: - - // Standard data needed to trigger a callback in lua - lua_State* m_L; - int m_self; - int m_callback; - - // Data to be passed to lua - char* m_userID; - int m_status; - - // used for determining when to trigger a callback - CallbackState m_cbState; - }; - - // Holds data recevied from the callback for the Apple sign in flow - // so that we can access it on the main thread, since the Apple callback - // can be triggered in any arbitrary thread context. - struct AuthCallbackData{ - public: - - // Standard data needed to trigger a callback in lua - lua_State* m_L; - int m_self; - int m_callback; - - // Data to be passed to lua on a successful authorization - char* m_identityToken; - char* m_userID; - char* m_email; - char* m_firstName; - char* m_familyName; - int m_userStatus; - - // Data to be passed to lua on a failed authorization - char* m_message; - - // used for determining when to trigger a callback - CallbackState m_cbState; - bool m_success; - }; - - // Sets up data and mutexes to handle capturing data from Apple callbacks - // and running our own associated callbacks on the main thread. - void InitCallbacks(); - - // Check if an callbacks have been queued since the last updated on the main - // thread, and if so, trigger the callback. - void CheckForQueuedCallbacks(); - - // Common setup to run each time we want to trigger a callback in lua. - bool RunCallbackSetup(lua_State* L, int _self, int _callback); - - // One-time setup for credential checking callbacks. - void InitCredentialCallbackData(); - // Setup to be run as the credential checking callback is set to idle - void ResetCredentialCallbackData(); - // Queue the credential check callback to be triggered next update call in the main thread. - void QueueCredentialCallback(lua_State* L, int _self, int _callback, char* userID, int status); - // Check if the credential check callback was queued to run in the next update call in the main thread. - void CheckForCredentialCallback(); - // Setup the credential check callback to be called in lua on the main thread, and trigger it - void RunCredentialCallback(); - - // One-time setup for sign in authorization callbacks. - void InitAuthCallbackData(); - // Setup to be run as the sign in authorizationcallback is set to idle - void ResetAuthCallbackData(); - // Queue the sign in authorization callback to be triggered next update call in the main thread, when authorization succeeds. - void QueueAuthSuccessCallback(lua_State* L, int _self, int _callback, const char* identityToken, const char* userID, const char* email, const char* firstName, const char* familyName, int userStatus); - // Queue the sign in authorization callback to be triggered next update call in the main thread, when authorization fails. - void QueueAuthFailureCallback(lua_State* L, int _self, int _callback, const char* message); - // Check if the sign in authorization callback was queued to run in the next update call in the main thread. - void CheckForAuthCallback(); - // Setup the sign in authorization callback for when authorization succeeds to be called in lua on the main thread, and trigger it - void RunAuthSuccessCallback(); - // Setup the sign in authorization callback for when authorization fails to be called in lua on the main thread, and trigger it - void RunAuthFailureCallback(); -} diff --git a/siwa/src/siwa_ios.h b/siwa/src/siwa_ios.h deleted file mode 100755 index 3d225c9..0000000 --- a/siwa/src/siwa_ios.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#if defined(DM_PLATFORM_IOS) - -struct lua_State; - -namespace siwa_code { -namespace platform { - - // Standard Defold Native Extension functions - void AppInitialize(); - void AppFinalize(); - void Initialize(); - void Update(); - void Finalize(); - - // Trigged by a call from lua to check if sign in with apple is supported on this device. - int IsSiwaSupported(lua_State* L); - - // Triggered by a call from lua to start the sign in with apple flow - // expects the callback to be a reference number to the lua registry - // expects the context to be reference number to the lua registry - int AuthenticateWithApple(lua_State* L, int callback, int context, lua_State* thread); - - // Triggered by a call from lua to check if a provided apple id grants this app permission to use that id. - // expects the callback to be a reference number to the lua registry - // expects the context to be reference number to the lua registry - int CheckStatusOfAppleID(lua_State* L, char* userID, int callback, int context, lua_State* thread); - - -} // namespace -} // namespace - -#endif diff --git a/siwa/src/siwa_ios.mm b/siwa/src/siwa_ios.mm index 22d33bf..a258265 100755 --- a/siwa/src/siwa_ios.mm +++ b/siwa/src/siwa_ios.mm @@ -1,58 +1,14 @@ #if defined(DM_PLATFORM_IOS) -#include "siwa_ios.h" - -#include "siwa_callbacks.h" +#include "siwa.h" #include -#include -#include -#include #include #include #include -// Data used to ensure we only have 1 request in progress with Apples servers at a time. -// Access should be restricted behind mutex protection as the callbacks from apple can -// be triggered in arbitrary contexts. -// This data is also used to hold information passed from lua until the callback comes -// back from Apple, so that we may pass it back to lua to identify the request we are -// getting a callback for. -struct SiwaData -{ - SiwaData() { - memset(this, 0, sizeof(*this)); - m_userID = nullptr; - m_Callback = LUA_NOREF; - m_Self = LUA_NOREF; - - m_waitingOnCallback = false; - } - - // the user ID used for checking credential state - char* m_userID; - - // references the lua state, script instance and function callback - // to be triggered when the request to apple is complete. - int m_Callback; - int m_Self; - lua_State* m_MainThread; - - // used to prevent multiple requests to apple being in progress at once. - bool m_waitingOnCallback; -}; - -static SiwaData g_SiwaData; -static dmMutex::HMutex g_siwaMutex; - -static void CleanupSiwaData() { - g_SiwaData.m_waitingOnCallback = false; - g_SiwaData.m_MainThread = nullptr; - g_SiwaData.m_Self = LUA_NOREF; - g_SiwaData.m_Callback = LUA_NOREF; -} // The sign in with Apple flow expects us to have a delegate to which it can both pass data from the sign in flow // but also how to figure out in which UI context it should display the native login UI. @@ -81,21 +37,10 @@ - (instancetype) init // The possible results are revoked(0), authorized(1), and unknown(2). // In practice, we have recieved unknown when revoking permission to this test app // so we should treat both revoked and unknown as unauthorized. -- (void) checkCredentialStatus +- (void) getCredentialState { - char* user_id = nullptr; - { - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - user_id = g_SiwaData.m_userID; - } - - if(user_id == nullptr) - { - dmLogInfo("provided user id was NULL!"); - return; - } - - NSString* user_id_string = [[NSString alloc] initWithUTF8String:user_id]; + char* userId = Siwa_GetUserId(); + NSString* user_id_string = [[NSString alloc] initWithUTF8String:userId]; [self.m_idProvider getCredentialStateForUserID: user_id_string completion: ^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError* error) { @@ -106,28 +51,26 @@ - (void) checkCredentialStatus dmLogError("getCredentialStateForUserID completed with error"); } + SiwaCredentialState state = STATE_UNKNOWN; switch(credentialState) { case ASAuthorizationAppleIDProviderCredentialAuthorized: - dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialAuthorized"); + dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialAuthorized"); + state = STATE_AUTHORIZED; break; case ASAuthorizationAppleIDProviderCredentialRevoked: dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialRevoked"); + state = STATE_REVOKED; break; case ASAuthorizationAppleIDProviderCredentialNotFound: dmLogInfo("credential state: ASAuthorizationAppleIDProviderCredentialNotFound"); + state = STATE_NOT_FOUND; break; default: dmLogInfo("credential state: unknown!!!"); break; } - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - dmSiwa::QueueCredentialCallback(g_SiwaData.m_MainThread, g_SiwaData.m_Self, g_SiwaData.m_Callback, g_SiwaData.m_userID, credentialState); - - // now that the callback is queued, we can clean up the held data. - free(g_SiwaData.m_userID); - g_SiwaData.m_userID = nullptr; - CleanupSiwaData(); + Siwa_QueueCredentialCallback(userId, state); }]; } @@ -157,194 +100,83 @@ - (void)authorizationController:(ASAuthorizationController *)controller if ([authorization.credential class] == [ASAuthorizationAppleIDCredential class]) { ASAuthorizationAppleIDCredential* appleIdCredential = ((ASAuthorizationAppleIDCredential*) authorization.credential); - /////////////////////////////////////////////// - // Pull data out of the auth and translate to cpp types - /////////////////////////////////////////////// const char* appleUserId = [appleIdCredential.user UTF8String]; const char* email = [appleIdCredential.email UTF8String]; const char* givenName = [appleIdCredential.fullName.givenName UTF8String]; const char* familyName = [appleIdCredential.fullName.familyName UTF8String]; - const int userDetectionStatus = (int) appleIdCredential.realUserStatus; + SiwaUserDetectionStatus userDetectionStatus = STATUS_UNSUPPORTED; + if (appleIdCredential.realUserStatus == ASUserDetectionStatusLikelyReal) + { + userDetectionStatus = STATUS_LIKELY_REAL; + } + else if (appleIdCredential.realUserStatus == ASUserDetectionStatusUnknown) + { + userDetectionStatus = STATUS_UNKNOWN; + } + + appleIdCredential.realUserStatus; NSString* tokenString = [[NSString alloc] initWithData:appleIdCredential.identityToken encoding:NSUTF8StringEncoding]; const char* identityToken = [tokenString UTF8String]; - /////////////////////////////////////////////// - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - dmSiwa::QueueAuthSuccessCallback(g_SiwaData.m_MainThread, g_SiwaData.m_Self, g_SiwaData.m_Callback, identityToken, appleUserId, email, givenName, familyName, userDetectionStatus); - CleanupSiwaData(); + Siwa_QueueAuthSuccessCallback(identityToken, appleUserId, email, givenName, familyName, userDetectionStatus); } else { - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - dmSiwa::QueueAuthFailureCallback(g_SiwaData.m_MainThread, g_SiwaData.m_Self, g_SiwaData.m_Callback, "authorization failed!"); - CleanupSiwaData(); + Siwa_QueueAuthFailureCallback("authorization failed!"); } } // The Auth controller callback for getting an error during authorization - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error { - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - dmSiwa::QueueAuthFailureCallback(g_SiwaData.m_MainThread, g_SiwaData.m_Self, g_SiwaData.m_Callback, "authorization error!"); - CleanupSiwaData(); + Siwa_QueueAuthFailureCallback("authorization error!"); } @end -namespace siwa_code { -namespace platform { - - void AppInitialize() { - g_siwaMutex = dmMutex::New(); - dmSiwa::InitCallbacks(); - } - - void AppFinalize() { - } - - void Initialize() { - } - - void Update() { - dmSiwa::CheckForQueuedCallbacks(); - } - - void Finalize() { - } - - // Checks if Siwa is supported on this device by seeing if the main - // class involved in all the siwa requests we use exists. - // Used by the native code side of this module. - static bool IsSiwaAvailable() - { - return ([ASAuthorizationAppleIDProvider class] != nil); - } - - // Checks if Siwa is supported on this device. - // Exposed to lua. - int IsSiwaSupported(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 1); - - if(IsSiwaAvailable()) - { - lua_pushboolean(L, 1); - } - else - { - lua_pushboolean(L, 0); - } - - return 1; - } - - //Cleans up callback data so that its fresh for the next callback. - //!!! Not Thread safe on g_siwaData !!! - //Make sure whatever is calling this wraps a mutex around this call - static void CallbackSetup(lua_State* L) - { - if (g_SiwaData.m_Callback != LUA_NOREF) { - dmLogError("Unexpected callback set"); - - dmScript::Unref(L, LUA_REGISTRYINDEX, g_SiwaData.m_Callback); - dmScript::Unref(L, LUA_REGISTRYINDEX, g_SiwaData.m_Self); - g_SiwaData.m_Callback = LUA_NOREF; - g_SiwaData.m_Self = LUA_NOREF; - } - - if(g_SiwaData.m_userID != nullptr){ - free(g_SiwaData.m_userID); - g_SiwaData.m_userID = nullptr; - } - } - API_AVAILABLE(ios(13.0)) - static SiwaManager* g_siwaManager = nil; +API_AVAILABLE(ios(13.0)) +static SiwaManager* g_SiwaManager = nil; - API_AVAILABLE(ios(13.0)) - static SiwaManager* GetSiwaManager() +API_AVAILABLE(ios(13.0)) +SiwaManager* GetSiwaManager() +{ + if(g_SiwaManager == nil) { - if(g_siwaManager == nil) - { - g_siwaManager = [[SiwaManager alloc] init]; - } - - return g_siwaManager; - } - - API_AVAILABLE(ios(13.0)) - // Kicks off the request to check the credential state of a provided user id. - void DoCheckStatus(lua_State* L, char* userID, int callback, int context, lua_State* thread) { - { - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - if(g_SiwaData.m_waitingOnCallback) - { - dmLogError("ERROR: Callback already in progress, abort"); - return; - } - - CallbackSetup(L); - - g_SiwaData.m_userID = userID; //taking ownership - g_SiwaData.m_Callback = callback; - g_SiwaData.m_Self = context; - g_SiwaData.m_MainThread = thread; - - g_SiwaData.m_waitingOnCallback = true; - } - - SiwaManager *siwaMan = GetSiwaManager(); - [siwaMan checkCredentialStatus]; - return; - } - - API_AVAILABLE(ios(13.0)) - // Kicks off the sign in with apple flow. - void DoAuthentication(lua_State* L, int callback, int context, lua_State* thread){ - { - DM_MUTEX_SCOPED_LOCK(g_siwaMutex) - if(g_SiwaData.m_waitingOnCallback) - { - dmLogError("ERROR: Callback already in progress, abort"); - return; - } - - CallbackSetup(L); - - g_SiwaData.m_Callback = callback; - g_SiwaData.m_Self = context; - g_SiwaData.m_MainThread = thread; - - g_SiwaData.m_waitingOnCallback = true; - } - - SiwaManager *siwaMan = GetSiwaManager(); - [siwaMan loginWithUI]; + g_SiwaManager = [[SiwaManager alloc] init]; } - int CheckStatusOfAppleID(lua_State* L, char* userID, int callback, int context, lua_State* thread) { - DM_LUA_STACK_CHECK(L, 0); - - if(IsSiwaAvailable()) - { - DoCheckStatus(L, userID, callback, context, thread); - } + return g_SiwaManager; +} - return 0; - } +API_AVAILABLE(ios(13.0)) +// Kicks off the request to get the credential state of a provided user id. +void Siwa_PlatformDoGetCredentialState() { + SiwaManager *siwaMan = GetSiwaManager(); + [siwaMan getCredentialState]; + return; +} - int AuthenticateWithApple(lua_State* L, int callback, int context, lua_State* thread) { - DM_LUA_STACK_CHECK(L, 0); +API_AVAILABLE(ios(13.0)) +// Kicks off the sign in with apple flow. +void Siwa_PlatformDoAuthenticateWithApple() { + SiwaManager *siwaMan = GetSiwaManager(); + [siwaMan loginWithUI]; +} - if(IsSiwaAvailable()) - { - DoAuthentication(L, callback, context, thread); - } +// Checks if Siwa is supported on this device by seeing if the main +// class involved in all the siwa requests we use exists. +bool Siwa_PlatformIsSupported() +{ + return ([ASAuthorizationAppleIDProvider class] != nil); +} - return 0; - } +void Siwa_PlatformGetCredentialState() { + Siwa_PlatformDoGetCredentialState(); +} -} // namespace -} // namespace +void Siwa_PlatformAuthenticateWithApple() { + Siwa_PlatformDoAuthenticateWithApple(); +} #endif diff --git a/siwa/src/siwa_main.cpp b/siwa/src/siwa_main.cpp deleted file mode 100755 index e745a5b..0000000 --- a/siwa/src/siwa_main.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#if defined(DM_PLATFORM_IOS) - -#include "siwa_ios.h" -#include -#include -#include -#include -#include - - -#define MODULE_NAME "siwa" - -namespace siwa_code { - -static int ReturnFalse(lua_State* L) { - DM_LUA_STACK_CHECK(L, 1); - lua_pushboolean(L, 0); - return 1; -} - -static int ReturnNil(lua_State* L){ - DM_LUA_STACK_CHECK(L, 1); - lua_pushnil(L); - return 1; -} - -// Function for asking Apple if a provided user id currently grants us access to it for this app. -// Expects: (a string containing the user id, and a callback that expects a self instance and a table containing results). -// The callback will be triggered at some point later after getting a response from Apple. -// The response will be SUCCESS whether or not the id granted us permission. -// It will give us a status of 1 if the user is authorized, and 2 if it's not. -static int CheckStatusOfAppleID(lua_State* L){ - DM_LUA_STACK_CHECK(L, 0); - - int num_args = lua_gettop(L); - if(num_args != 2){ - dmLogError("Incorrect number of args passed to CheckStatusOfAppleID!"); - return ReturnFalse(L); - } - - luaL_checktype(L, 1, LUA_TSTRING); - char* userID = strdup(lua_tostring(L, 1)); - - luaL_checktype(L, 2, LUA_TFUNCTION); - lua_pushvalue(L, 2); - int callback = dmScript::Ref(L, LUA_REGISTRYINDEX); - - dmScript::GetInstance(L); - int context = dmScript::Ref(L, LUA_REGISTRYINDEX); - - lua_State* thread = dmScript::GetMainThread(L); - - if (platform::IsSiwaSupported(L)) { - platform::CheckStatusOfAppleID(L, userID, callback, context, thread); - } - - return 0; -} - -// Function for having a user sign in with Apple. -// Expects: (a callback that expects a self instance and a table containing results). -// The callback will be triggered at some point later after getting a response from Apple. -// The first time a user signs in after granting permission to this app, the callback will give us a name and email -// as well as a user id and identity token on success. -// On subsequent logins, we will only get the user id and identity token. -// On a failure, such as when the user cancels the login flow, we will get ERROR as a result, and a message -// describing the error. -static int AuthenticateWithApple(lua_State* L){ - DM_LUA_STACK_CHECK(L, 0); - - luaL_checktype(L, 1, LUA_TFUNCTION); - - lua_pushvalue(L, 1); - int callback = dmScript::Ref(L, LUA_REGISTRYINDEX); - - dmScript::GetInstance(L); - int context = dmScript::Ref(L, LUA_REGISTRYINDEX); - - lua_State* thread = dmScript::GetMainThread(L); - - if (platform::IsSiwaSupported(L)) { - platform::AuthenticateWithApple(L, callback, context, thread); - } - - return 0; -} - -const luaL_reg lua_register[] = -{ - {"is_siwa_supported", platform::IsSiwaSupported}, - {"check_credentials_status", CheckStatusOfAppleID}, - {"authenticate", AuthenticateWithApple}, - {0, 0} -}; - -static dmExtension::Result AppInitialize(dmExtension::AppParams* params) -{ - platform::AppInitialize(); - return dmExtension::RESULT_OK; -} - -static dmExtension::Result AppFinalize(dmExtension::AppParams* params) -{ - platform::AppFinalize(); - return dmExtension::RESULT_OK; -} - -static dmExtension::Result Initialize(dmExtension::Params* params) -{ - // Register lua functions - { - lua_State* L = params->m_L; - int top = lua_gettop(L); - luaL_register(L, MODULE_NAME, lua_register); - lua_pop(L, 1); - assert(top == lua_gettop(L)); - } - - platform::Initialize(); - return dmExtension::RESULT_OK; -} - -static dmExtension::Result Update(dmExtension::Params* params) -{ - platform::Update(); - return dmExtension::RESULT_OK; -} - -static dmExtension::Result Finalize(dmExtension::Params* params) -{ - platform::Finalize(); - return dmExtension::RESULT_OK; -} - -} // namespace - -DM_DECLARE_EXTENSION(siwa, MODULE_NAME, siwa_code::AppInitialize, siwa_code::AppFinalize, siwa_code::Initialize, siwa_code::Update, 0, siwa_code::Finalize); - -#endif