Initial push

This commit is contained in:
Nick Leeman 2021-02-25 00:51:33 +01:00
parent 13844c3e93
commit 1525e40378
16 changed files with 1508 additions and 0 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
github: defold
patreon: Defold
custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NBNBHTUW4GS4C']

View File

@ -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'
}
}]

10
.gitignore vendored Executable file
View File

@ -0,0 +1,10 @@
/.internal
/build
.externalToolBuilders
.DS_Store
Thumbs.db
.lock-wscript
*.pyc
.project
.cproject
builtins

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Defold
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

66
docs/index.md Normal file
View File

@ -0,0 +1,66 @@
---
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 apps 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.
## Usage
### Check Sign in with Apple support
```Lua
if siwa.is_supported() then
print("Sign in with Apple is supported")
end)
```
### Trigger Sign in with Apple
```Lua
siwa.authenticate(id, function(self, data)
print(data.identity_token)
print(data.user_id)
print(data.first_name, data.family_name)
print(data.email)
if data.user_status == siwa.STATUS_LIKELY_REAL then
print("Likely a real person")
end
end)
```
### Check credential state
```Lua
siwa.get_credential_state(id, function(self, data)
if data.credential_state == siwa.STATE_AUTHORIZED then
print("User has still authorized the application", data.user_id)
elseif data.credential_state == siwa.STATE_REVOKED then
print("User has revoked authorization for the application", data.user_id)
end
end)
```
## Source code
The source code is available on [GitHub](https://github.com/defold/extension-siwa)
## API reference

20
game.project Executable file
View File

@ -0,0 +1,20 @@
[project]
title = extension-siwa
dependencies = https://github.com/andsve/dirtylarry/archive/master.zip
[script]
shared_state = 1
[library]
include_dirs = siwa
[bootstrap]
main_collection = /main/main.collectionc
[display]
width = 640
height = 1136
[ios]
bundle_identifier = com.defold.extension.siwa

4
input/game.input_binding Executable file
View File

@ -0,0 +1,4 @@
mouse_trigger {
input: MOUSE_BUTTON_1
action: "touch"
}

37
main/main.collection Executable file
View File

@ -0,0 +1,37 @@
name: "default"
scale_along_z: 0
embedded_instances {
id: "main_go"
data: "components {\n"
" id: \"main\"\n"
" component: \"/main/main.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
"}\n"
""
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale3 {
x: 1.0
y: 1.0
z: 1.0
}
}

551
main/main.gui Executable file
View File

@ -0,0 +1,551 @@
script: "/main/main.gui_script"
fonts {
name: "system_font"
font: "/builtins/fonts/system_font.font"
}
background_color {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
nodes {
position {
x: 114.0
y: 418.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 0.5
y: 0.5
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_TEMPLATE
id: "login"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.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: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "login/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "login"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.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: "Login"
font: "larryfont"
id: "login/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.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: "login/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 114.0
y: 333.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 0.5
y: 0.5
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_TEMPLATE
id: "check"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.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: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "check/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "check"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.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: "Check"
font: "larryfont"
id: "check/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.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: "check/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 114.0
y: 255.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 0.5
y: 0.5
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_TEMPLATE
id: "check_fail"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.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: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "check_fail/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "check_fail"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.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: "Check\n"
"Fail"
font: "larryfont"
id: "check_fail/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.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: "check_fail/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8
template_node_child: true
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

64
main/main.gui_script Executable file
View File

@ -0,0 +1,64 @@
local dl = require("dirtylarry.dirtylarry")
local lines = {}
local function log(msg, ...)
msg = msg:format(...)
print(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 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
local function authenticate(self)
log("authenticate")
siwa.authenticate(function(self, data)
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_supported()
end
function init(self)
msg.post(".", "acquire_input_focus")
if is_siwa_supported() then
log("SIWA supported!")
else
log("SIWA not supported...")
end
end
function on_input(self, action_id, action)
if is_siwa_supported() then
dl:button("check", action_id, action, function()
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, "foobar")
end)
dl:button("login", action_id, action, function()
authenticate(self)
end)
end
end

115
siwa/api/siwa.script_api Normal file
View File

@ -0,0 +1,115 @@
- 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 (user_id, credential_state)
examples:
- desc: |-
```lua
siwa.get_credential_state(id, function(self, data)
if data.credential_state == siwa.STATE_AUTHORIZED then
print("User has still authorized the application", data.user_id)
elseif data.credential_state == siwa.STATE_REVOKED then
print("User has revoked authorization for the application", data.user_id)
end
end)
```
#*****************************************************************************************************
- 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 (user_id, identity_token, email, first_name, family_name, status, result)
examples:
- desc: |-
```lua
siwa.authenticate(function(self, data)
print(data.identity_token)
print(data.user_id)
print(data.first_name, data.family_name)
print(data.email)
if data.user_status == siwa.STATUS_LIKELY_REAL then
print("Likely a real person")
end
end)
```
#*****************************************************************************************************
- name: STATE_NOT_FOUND
type: number
desc: The user cant 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 hasnt determined whether the user might be a real person.
- name: STATUS_UNSUPPORTED
type: number
desc: The system cant determine this users status as a real person.
- name: STATUS_LIKELY_REAL
type: number
desc: The user appears to be a real person.

30
siwa/ext.manifest Executable file
View File

@ -0,0 +1,30 @@
name: siwa
platforms:
x86-osx:
context:
frameworks: []
flags: ["-std=c++11", "-stdlib=libc++"]
linkFlags: ["-stdlib=libc++"]
libs: ["c++"]
x86_64-osx:
context:
frameworks: []
flags: ["-std=c++11", "-stdlib=libc++"]
libs: ["c++"]
x86_64-ios:
context:
frameworks: ['AuthenticationServices']
defines: ['SUPPORTS_SIWA']
armv7-ios:
context:
frameworks: ['AuthenticationServices']
defines: ['SUPPORTS_SIWA']
arm64-ios:
context:
frameworks: ['AuthenticationServices']
defines: ['SUPPORTS_SIWA']

292
siwa/src/siwa.cpp Executable file
View File

@ -0,0 +1,292 @@
#if defined(DM_PLATFORM_IOS)
#include "siwa.h"
#include <dmsdk/sdk.h>
#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

86
siwa/src/siwa.h Executable file
View File

@ -0,0 +1,86 @@
#pragma once
#if defined(DM_PLATFORM_IOS)
#include <dmsdk/sdk.h>
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

184
siwa/src/siwa_ios.mm Executable file
View File

@ -0,0 +1,184 @@
#if defined(DM_PLATFORM_IOS)
#include "siwa.h"
#include <AuthenticationServices/AuthenticationServices.h>
#include <dmsdk/sdk.h>
#include <stdlib.h>
#include <string.h>
// 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.
// This class is that delegate.
// It also owns the provider that is both used for the sign in flow, as well as for credential state checking.
API_AVAILABLE(ios(13.0))
@interface SiwaManager : NSObject <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
@property (nonatomic, strong) ASAuthorizationAppleIDProvider *m_idProvider;
@end
@implementation SiwaManager
- (instancetype) init
{
self = [super init];
if (self)
{
self.m_idProvider = [[ASAuthorizationAppleIDProvider alloc] init];
}
return self;
}
// Check if the user id provided to use from lua still grants our app permission
// to use it for sign in. User's can revoke app's permissions to use the id for sign in
// at any time, so we need to be able to monitor this.
// 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) getCredentialState
{
char* userId = Siwa_GetUserId();
NSString* user_id_string = [[NSString alloc] initWithUTF8String:userId];
[self.m_idProvider getCredentialStateForUserID: user_id_string
completion: ^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError* error) {
// TODO: docs provide no information about what type of errors we can expect:
// https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider/3175423-getcredentialstateforuserid?language=objc
if (error) {
NSString *errorMessage = [NSString stringWithFormat: @"getCredentialStateForUserID completed with error: %@", [error localizedDescription]];
dmLogError([errorMessage UTF8String]);
}
SiwaCredentialState state = STATE_UNKNOWN;
switch(credentialState) {
case 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;
}
Siwa_QueueCredentialCallback(userId, state);
}];
}
// triggers the sign in with Apple native ui flow to begin.
- (void) loginWithUI
{
ASAuthorizationAppleIDRequest* request = [self.m_idProvider createRequest];
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
ASAuthorizationController* authController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
authController.presentationContextProvider = self;
authController.delegate = self;
[authController performRequests];
}
// the Auth controller needs to specify where to display the native login.
// this is the function our SiwaManager delegate has to implement to provide that information.
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
return window;
}
// the Auth controller callback for getting a response back from apple for a sign in
- (void)authorizationController:(ASAuthorizationController *)controller
didCompleteWithAuthorization:(ASAuthorization *)authorization {
if ([authorization.credential class] == [ASAuthorizationAppleIDCredential class]) {
ASAuthorizationAppleIDCredential* appleIdCredential = ((ASAuthorizationAppleIDCredential*) authorization.credential);
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];
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];
Siwa_QueueAuthSuccessCallback(identityToken, appleUserId, email, givenName, familyName, userDetectionStatus);
}
else
{
Siwa_QueueAuthFailureCallback("authorization failed!");
}
}
// The Auth controller callback for getting an error during authorization
- (void)authorizationController:(ASAuthorizationController *)controller
didCompleteWithError:(NSError *)error {
NSString *errorMessage = [NSString stringWithFormat: @"Authorization error: %@", [error localizedDescription]];
Siwa_QueueAuthFailureCallback([errorMessage UTF8String]);
}
@end
API_AVAILABLE(ios(13.0))
static SiwaManager* g_SiwaManager = nil;
API_AVAILABLE(ios(13.0))
SiwaManager* GetSiwaManager()
{
if(g_SiwaManager == nil)
{
g_SiwaManager = [[SiwaManager alloc] init];
}
return g_SiwaManager;
}
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;
}
API_AVAILABLE(ios(13.0))
// Kicks off the sign in with apple flow.
void Siwa_PlatformDoAuthenticateWithApple() {
SiwaManager *siwaMan = GetSiwaManager();
[siwaMan loginWithUI];
}
// 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);
}
void Siwa_PlatformGetCredentialState() {
Siwa_PlatformDoGetCredentialState();
}
void Siwa_PlatformAuthenticateWithApple() {
Siwa_PlatformDoAuthenticateWithApple();
}
#endif

6
siwa/src/siwa_null.cpp Executable file
View File

@ -0,0 +1,6 @@
#if !defined(DM_PLATFORM_IOS)
extern "C" void siwa()
{
}
#endif