com.crossx.sdk.cocos-creator2 0.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxAndroidNativeClient.java +103 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxAndroidSecureStorage.java +138 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosActivityProvider.java +33 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosBridge.java +159 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosBridgeInstaller.java +9 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosNativeClient.java +28 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxOAuthBridge.kt +59 -0
- package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/PendingCROSSxCocosNativeClient.java +34 -0
- package/Plugins/Mac/Classes/CROSSxMacDeepLinkHandler.h +25 -0
- package/Plugins/Mac/Classes/CROSSxMacDeepLinkHandler.mm +229 -0
- package/Plugins/README.md +117 -0
- package/Plugins/iOS/Classes/CROSSxCocosBridge.h +40 -0
- package/Plugins/iOS/Classes/CROSSxCocosBridge.mm +176 -0
- package/Plugins/iOS/Classes/CROSSxCocosBridgeInstaller.h +7 -0
- package/Plugins/iOS/Classes/CROSSxCocosBridgeInstaller.mm +11 -0
- package/Plugins/iOS/Classes/CROSSxCocosOAuthWrapper.swift +58 -0
- package/Plugins/iOS/Classes/CROSSxIOSNativeClient.h +5 -0
- package/Plugins/iOS/Classes/CROSSxIOSNativeClient.mm +129 -0
- package/Plugins/iOS/Classes/CROSSxIOSSecureStorage.h +22 -0
- package/Plugins/iOS/Classes/CROSSxIOSSecureStorage.mm +67 -0
- package/Runtime/Adapters/DeepLink/CocosDeepLinkDispatcher.ts +98 -0
- package/Runtime/Adapters/DeepLink/CocosWebkitLifecycleDispatcher.ts +73 -0
- package/Runtime/Adapters/Gateway/GatewayWalletAdapter.ts +879 -0
- package/Runtime/Adapters/Mock/MockWalletAdapter.ts +137 -0
- package/Runtime/Adapters/Storage/CocosStorageAdapter.ts +153 -0
- package/Runtime/Adapters/Web/CrossySdkJsGlobal.ts +80 -0
- package/Runtime/Adapters/Web/WebOAuthAdapter.ts +13 -0
- package/Runtime/Adapters/Web/WebWalletAdapter.ts +122 -0
- package/Runtime/Adapters/Windows/WindowsOAuthAdapter.ts +60 -0
- package/Runtime/Adapters/Windows/WindowsWalletAdapter.ts +23 -0
- package/Runtime/Adapters/Windows/src/WindowsLoopbackCallbackAdapter.ts +254 -0
- package/Runtime/Core/Endpoints.ts +44 -0
- package/Runtime/Core/I18n/messages.ts +294 -0
- package/Runtime/Core/Ports/CROSSxSdkPorts.ts +15 -0
- package/Runtime/Core/Ports/IDeepLinkPort.ts +7 -0
- package/Runtime/Core/Ports/INativeBridgePort.ts +30 -0
- package/Runtime/Core/Ports/IOAuthPort.ts +5 -0
- package/Runtime/Core/Ports/IStoragePort.ts +6 -0
- package/Runtime/Core/Ports/IWalletPort.ts +40 -0
- package/Runtime/Core/Ports/NativeBridgeMethods.ts +75 -0
- package/Runtime/Core/Types/CROSSxSdkTypes.ts +313 -0
- package/Runtime/Core/UseCases/CROSSxSdkUseCases.ts +6 -0
- package/Runtime/Core/UseCases/GetAddressUseCase.ts +14 -0
- package/Runtime/Core/UseCases/LoginUseCase.ts +10 -0
- package/Runtime/Core/UseCases/LogoutUseCase.ts +9 -0
- package/Runtime/Core/UseCases/SendTransactionUseCase.ts +14 -0
- package/Runtime/Core/UseCases/SetupWalletUseCase.ts +10 -0
- package/Runtime/Core/UseCases/SignUseCase.ts +22 -0
- package/Runtime/Core/Utils/crosspay.ts +223 -0
- package/Runtime/Core/Utils/hexQuantity.ts +73 -0
- package/Runtime/Core/Utils/oauth.ts +179 -0
- package/Runtime/Core/Utils/schemes.ts +9 -0
- package/Runtime/SDK/CROSSxCocosSDK.ts +1734 -0
- package/Runtime/SDK/CROSSxCocosSDKFactory.ts +119 -0
- package/Runtime/SDK/CROSSxSdk.ts +2 -0
- package/Runtime/UI/CROSSxSdkUI.ts +15 -0
- package/Runtime/UI/CocosDesignTokens.ts +184 -0
- package/Runtime/UI/CocosSdkModal.ts +3656 -0
- package/Runtime/UI/SDKUIProvider.ts +167 -0
- package/Runtime/types.ts +62 -0
- package/editor/dist/build-plugin.js +60 -0
- package/editor/dist/build-processors/android-post-process.js +516 -0
- package/editor/dist/build-processors/file-copy.js +79 -0
- package/editor/dist/build-processors/index.js +22 -0
- package/editor/dist/build-processors/ios-post-process.js +783 -0
- package/editor/dist/build-processors/macos-post-process.js +382 -0
- package/editor/dist/build-processors/native-build-post-process.js +453 -0
- package/editor/dist/build-processors/native-dependencies.js +76 -0
- package/editor/dist/build-settings.js +213 -0
- package/editor/dist/main.js +44 -0
- package/package.json +43 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.net.Uri;
|
|
6
|
+
import java.lang.reflect.Method;
|
|
7
|
+
import org.json.JSONObject;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default Android native client.
|
|
11
|
+
*
|
|
12
|
+
* <p>OAuth is delegated to {@link CROSSxOAuthBridge}, which wraps the external
|
|
13
|
+
* {@code io.crosstoken.crossxwebkit.CustomTabsWebAuthAdapter} (provided via the
|
|
14
|
+
* CROSSx Android SDK BOM). When the Cocos {@link Activity} cannot be resolved or the
|
|
15
|
+
* OAuth flow fails to start, we fall back to a plain {@code ACTION_VIEW} intent so the
|
|
16
|
+
* user is never stranded. Wallet operations still need to be wired to the native SDK.</p>
|
|
17
|
+
*/
|
|
18
|
+
public class CROSSxAndroidNativeClient extends PendingCROSSxCocosNativeClient {
|
|
19
|
+
@Override
|
|
20
|
+
public Object openOAuth(JSONObject payload) throws Exception {
|
|
21
|
+
String authUrl = payload.optString("authUrl", "");
|
|
22
|
+
if (authUrl.length() == 0) {
|
|
23
|
+
throw new IllegalArgumentException("openOAuth requires authUrl");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
String callbackScheme = payload.optString("callbackScheme", "");
|
|
27
|
+
boolean nativeOpened = openWithCrossxOAuth(authUrl, callbackScheme);
|
|
28
|
+
if (!nativeOpened) {
|
|
29
|
+
openUrl(authUrl);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
JSONObject result = new JSONObject();
|
|
33
|
+
result.put("url", authUrl);
|
|
34
|
+
result.put("status", "opened");
|
|
35
|
+
result.put("nativeOAuth", nativeOpened);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public boolean handleCallbackUrl(String url) {
|
|
40
|
+
if (url == null || url.length() == 0) return false;
|
|
41
|
+
// Our own bridge is a sibling in the same package — call it directly so this
|
|
42
|
+
// path is robust against ProGuard / R8 stripping the reflection target.
|
|
43
|
+
if (CROSSxOAuthBridge.handleCallbackUrl(url)) return true;
|
|
44
|
+
// Fall back to the Unity SDK bridge if a Unity OAuth flow was started in the
|
|
45
|
+
// same process (e.g. side-by-side integration). This is a soft dependency, so
|
|
46
|
+
// we keep it as reflection.
|
|
47
|
+
return invokeUnityBridgeBoolean("handleCallbackUrl", new Class<?>[] { String.class }, new Object[] { url });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private boolean openWithCrossxOAuth(String authUrl, String callbackScheme) {
|
|
51
|
+
if (callbackScheme == null || callbackScheme.length() == 0) return false;
|
|
52
|
+
// Compile-time call to our own OAuth bridge — this guarantees we know exactly
|
|
53
|
+
// whether the flow actually started, instead of relying on reflection swallowing
|
|
54
|
+
// a silent "no activity" return as success. If no activity is available right
|
|
55
|
+
// now we report false so the caller falls back to ACTION_VIEW.
|
|
56
|
+
if (CROSSxCocosActivityProvider.currentActivity() != null) {
|
|
57
|
+
try {
|
|
58
|
+
CROSSxOAuthBridge.startOAuthFlow(authUrl, callbackScheme);
|
|
59
|
+
return true;
|
|
60
|
+
} catch (Throwable ignored) {
|
|
61
|
+
// Fall through to the Unity legacy bridge and finally to ACTION_VIEW.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return invokeUnityBridgeVoid(
|
|
65
|
+
"startOAuthFlow",
|
|
66
|
+
new Class<?>[] { String.class, String.class },
|
|
67
|
+
new Object[] { authUrl, callbackScheme }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private boolean invokeUnityBridgeVoid(String methodName, Class<?>[] argTypes, Object[] args) {
|
|
72
|
+
try {
|
|
73
|
+
Class<?> bridge = Class.forName("com.nexus.cross.sdk.unity.CROSSxOAuthBridge");
|
|
74
|
+
Method method = bridge.getMethod(methodName, argTypes);
|
|
75
|
+
method.invoke(null, args);
|
|
76
|
+
return true;
|
|
77
|
+
} catch (Exception ignored) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private boolean invokeUnityBridgeBoolean(String methodName, Class<?>[] argTypes, Object[] args) {
|
|
83
|
+
try {
|
|
84
|
+
Class<?> bridge = Class.forName("com.nexus.cross.sdk.unity.CROSSxOAuthBridge");
|
|
85
|
+
Method method = bridge.getMethod(methodName, argTypes);
|
|
86
|
+
Object result = method.invoke(null, args);
|
|
87
|
+
return result instanceof Boolean && (Boolean) result;
|
|
88
|
+
} catch (Exception ignored) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected void openUrl(String url) {
|
|
94
|
+
Activity activity = CROSSxCocosActivityProvider.currentActivity();
|
|
95
|
+
if (activity == null) {
|
|
96
|
+
throw new IllegalStateException("Cocos Android Activity is unavailable");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
|
100
|
+
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
|
101
|
+
activity.startActivity(intent);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.SharedPreferences;
|
|
6
|
+
import android.os.Build;
|
|
7
|
+
import android.security.keystore.KeyGenParameterSpec;
|
|
8
|
+
import android.security.keystore.KeyProperties;
|
|
9
|
+
import android.util.Base64;
|
|
10
|
+
import java.security.KeyStore;
|
|
11
|
+
import java.util.Arrays;
|
|
12
|
+
import javax.crypto.Cipher;
|
|
13
|
+
import javax.crypto.KeyGenerator;
|
|
14
|
+
import javax.crypto.SecretKey;
|
|
15
|
+
import javax.crypto.spec.GCMParameterSpec;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Secure token storage backed by Android Keystore + AES-256-GCM.
|
|
19
|
+
*
|
|
20
|
+
* <p>On API 23+ (Marshmallow), values are encrypted with a key stored in the
|
|
21
|
+
* Android Keystore (hardware-backed on supported devices). On older API levels
|
|
22
|
+
* values are stored unencrypted in a MODE_PRIVATE SharedPreferences file as a
|
|
23
|
+
* best-effort fallback.
|
|
24
|
+
*
|
|
25
|
+
* <p>Requires compileSdkVersion >= 23 in the host project's build.gradle.
|
|
26
|
+
* No additional Gradle dependencies are needed — all APIs are part of the
|
|
27
|
+
* standard Android SDK.
|
|
28
|
+
*
|
|
29
|
+
* <p>Called from TypeScript via jsb.reflection.callStaticMethod:
|
|
30
|
+
* <ul>
|
|
31
|
+
* <li>getItem(String key) → (Ljava/lang/String;)Ljava/lang/String;</li>
|
|
32
|
+
* <li>setItem(String key, String val) → (Ljava/lang/String;Ljava/lang/String;)V</li>
|
|
33
|
+
* <li>removeItem(String key) → (Ljava/lang/String;)V</li>
|
|
34
|
+
* <li>clear() → ()V</li>
|
|
35
|
+
* </ul>
|
|
36
|
+
*/
|
|
37
|
+
public final class CROSSxAndroidSecureStorage {
|
|
38
|
+
private static final String KEYSTORE_ALIAS = "crossx_sdk_storage_key";
|
|
39
|
+
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
|
|
40
|
+
private static final String PREFS_FILE = "crossx_secure_prefs";
|
|
41
|
+
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
|
42
|
+
private static final int IV_SIZE = 12;
|
|
43
|
+
private static final int TAG_BIT_LENGTH = 128;
|
|
44
|
+
|
|
45
|
+
private CROSSxAndroidSecureStorage() {}
|
|
46
|
+
|
|
47
|
+
public static String getItem(String key) {
|
|
48
|
+
Activity activity = CROSSxCocosActivityProvider.currentActivity();
|
|
49
|
+
if (activity == null || key == null) return null;
|
|
50
|
+
String stored = prefs(activity).getString(key, null);
|
|
51
|
+
if (stored == null) return null;
|
|
52
|
+
if (Build.VERSION.SDK_INT < 23) return stored;
|
|
53
|
+
try {
|
|
54
|
+
return decrypt(stored);
|
|
55
|
+
} catch (Exception e) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public static void setItem(String key, String value) {
|
|
61
|
+
Activity activity = CROSSxCocosActivityProvider.currentActivity();
|
|
62
|
+
if (activity == null || key == null || value == null) return;
|
|
63
|
+
String stored = value;
|
|
64
|
+
if (Build.VERSION.SDK_INT >= 23) {
|
|
65
|
+
try {
|
|
66
|
+
stored = encrypt(value);
|
|
67
|
+
} catch (Exception ignored) {
|
|
68
|
+
// encryption failed — store plaintext so the app still works
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
prefs(activity).edit().putString(key, stored).apply();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public static void removeItem(String key) {
|
|
75
|
+
Activity activity = CROSSxCocosActivityProvider.currentActivity();
|
|
76
|
+
if (activity == null || key == null) return;
|
|
77
|
+
prefs(activity).edit().remove(key).apply();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static void clear() {
|
|
81
|
+
Activity activity = CROSSxCocosActivityProvider.currentActivity();
|
|
82
|
+
if (activity == null) return;
|
|
83
|
+
prefs(activity).edit().clear().apply();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── internals ─────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
private static SharedPreferences prefs(Context context) {
|
|
89
|
+
return context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@SuppressWarnings("NewApi")
|
|
93
|
+
private static SecretKey getOrCreateKey() throws Exception {
|
|
94
|
+
KeyStore ks = KeyStore.getInstance(ANDROID_KEYSTORE);
|
|
95
|
+
ks.load(null);
|
|
96
|
+
if (ks.containsAlias(KEYSTORE_ALIAS)) {
|
|
97
|
+
KeyStore.SecretKeyEntry entry =
|
|
98
|
+
(KeyStore.SecretKeyEntry) ks.getEntry(KEYSTORE_ALIAS, null);
|
|
99
|
+
if (entry != null) return entry.getSecretKey();
|
|
100
|
+
}
|
|
101
|
+
KeyGenerator kg = KeyGenerator.getInstance(
|
|
102
|
+
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE
|
|
103
|
+
);
|
|
104
|
+
kg.init(new KeyGenParameterSpec.Builder(
|
|
105
|
+
KEYSTORE_ALIAS,
|
|
106
|
+
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
|
107
|
+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
|
108
|
+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
109
|
+
.setKeySize(256)
|
|
110
|
+
.build());
|
|
111
|
+
return kg.generateKey();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@SuppressWarnings("NewApi")
|
|
115
|
+
private static String encrypt(String value) throws Exception {
|
|
116
|
+
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
|
117
|
+
cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey());
|
|
118
|
+
byte[] iv = cipher.getIV();
|
|
119
|
+
byte[] ciphertext = cipher.doFinal(value.getBytes("UTF-8"));
|
|
120
|
+
byte[] combined = new byte[IV_SIZE + ciphertext.length];
|
|
121
|
+
System.arraycopy(iv, 0, combined, 0, IV_SIZE);
|
|
122
|
+
System.arraycopy(ciphertext, 0, combined, IV_SIZE, ciphertext.length);
|
|
123
|
+
return Base64.encodeToString(combined, Base64.NO_WRAP);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@SuppressWarnings("NewApi")
|
|
127
|
+
private static String decrypt(String encoded) throws Exception {
|
|
128
|
+
byte[] combined = Base64.decode(encoded, Base64.NO_WRAP);
|
|
129
|
+
if (combined.length <= IV_SIZE) throw new IllegalArgumentException("Invalid ciphertext");
|
|
130
|
+
byte[] iv = Arrays.copyOfRange(combined, 0, IV_SIZE);
|
|
131
|
+
byte[] ciphertext = Arrays.copyOfRange(combined, IV_SIZE, combined.length);
|
|
132
|
+
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
|
133
|
+
cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(),
|
|
134
|
+
new GCMParameterSpec(TAG_BIT_LENGTH, iv));
|
|
135
|
+
byte[] plaintext = cipher.doFinal(ciphertext);
|
|
136
|
+
return new String(plaintext, "UTF-8");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the currently attached Cocos {@link Activity} for native SDK calls
|
|
7
|
+
* that need a presentation context (OAuth Custom Tabs, deep links, etc.).
|
|
8
|
+
*
|
|
9
|
+
* <p>Cocos Creator 3.x exposes the activity via {@code com.cocos.lib.GlobalObject#getActivity()};
|
|
10
|
+
* {@code CocosActivity.onCreate} registers itself there via {@code GlobalObject.init(context, this)}.
|
|
11
|
+
* The legacy Cocos2d-x location ({@code org.cocos2dx.lib.CocosHelper}) is kept as a fallback
|
|
12
|
+
* for older host projects. Both lookups are done via reflection so this class stays loadable in
|
|
13
|
+
* unit tests / standalone library builds where the Cocos engine is not on the classpath.</p>
|
|
14
|
+
*/
|
|
15
|
+
final class CROSSxCocosActivityProvider {
|
|
16
|
+
private CROSSxCocosActivityProvider() {}
|
|
17
|
+
|
|
18
|
+
static Activity currentActivity() {
|
|
19
|
+
// Cocos Creator 2.x (Cocos2d-x legacy)
|
|
20
|
+
try {
|
|
21
|
+
Class<?> clazz = Class.forName("org.cocos2dx.lib.CocosHelper");
|
|
22
|
+
Object activity = clazz.getMethod("getActivity").invoke(null);
|
|
23
|
+
if (activity instanceof Activity) return (Activity) activity;
|
|
24
|
+
} catch (Exception ignored) {}
|
|
25
|
+
// Cocos Creator 3.x
|
|
26
|
+
try {
|
|
27
|
+
Class<?> clazz = Class.forName("com.cocos.lib.GlobalObject");
|
|
28
|
+
Object activity = clazz.getMethod("getActivity").invoke(null);
|
|
29
|
+
if (activity instanceof Activity) return (Activity) activity;
|
|
30
|
+
} catch (Exception ignored) {}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import android.content.Intent;
|
|
4
|
+
import android.net.Uri;
|
|
5
|
+
import java.lang.reflect.Method;
|
|
6
|
+
import org.json.JSONException;
|
|
7
|
+
import org.json.JSONObject;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cocos JSB entrypoint for Android.
|
|
11
|
+
*
|
|
12
|
+
* Unity SDK pattern:
|
|
13
|
+
* - Kotlin plugin + Maven repository are injected at build time.
|
|
14
|
+
* - CROSSx Android SDK is resolved from Maven using the CROSSx BOM.
|
|
15
|
+
* - This bridge stays as source in the SDK package and delegates to native SDK APIs.
|
|
16
|
+
*/
|
|
17
|
+
public final class CROSSxCocosBridge {
|
|
18
|
+
private static CROSSxCocosNativeClient nativeClient = new PendingCROSSxCocosNativeClient();
|
|
19
|
+
|
|
20
|
+
private CROSSxCocosBridge() {}
|
|
21
|
+
|
|
22
|
+
public static void setNativeClient(CROSSxCocosNativeClient client) {
|
|
23
|
+
nativeClient = client == null ? new PendingCROSSxCocosNativeClient() : client;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static String request(String payload) {
|
|
27
|
+
try {
|
|
28
|
+
JSONObject request = new JSONObject(payload == null ? "{}" : payload);
|
|
29
|
+
String method = request.optString("method", "");
|
|
30
|
+
JSONObject methodPayload = normalizePayload(request);
|
|
31
|
+
|
|
32
|
+
return ok(dispatch(method, methodPayload));
|
|
33
|
+
} catch (JSONException e) {
|
|
34
|
+
return error("INVALID_REQUEST", e.getMessage());
|
|
35
|
+
} catch (UnsupportedOperationException e) {
|
|
36
|
+
return error("NATIVE_METHOD_PENDING", e.getMessage());
|
|
37
|
+
} catch (Exception e) {
|
|
38
|
+
return error("NATIVE_BRIDGE_ERROR", e.getMessage());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static boolean handleDeepLink(Intent intent) {
|
|
43
|
+
if (intent == null) return false;
|
|
44
|
+
Uri data = intent.getData();
|
|
45
|
+
return data != null && handleDeepLink(data.toString());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static boolean handleDeepLink(String url) {
|
|
49
|
+
if (url == null || url.length() == 0) return false;
|
|
50
|
+
if (nativeClient instanceof CROSSxAndroidNativeClient) {
|
|
51
|
+
((CROSSxAndroidNativeClient) nativeClient).handleCallbackUrl(url);
|
|
52
|
+
}
|
|
53
|
+
return dispatchDeepLinkToJs(url);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static JSONObject normalizePayload(JSONObject request) {
|
|
57
|
+
JSONObject payload = request.optJSONObject("payload");
|
|
58
|
+
return payload == null ? new JSONObject() : payload;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private static Object dispatch(String method, JSONObject payload) throws Exception {
|
|
62
|
+
switch (method) {
|
|
63
|
+
case "initialize": return nativeClient.initialize(payload);
|
|
64
|
+
case "signIn": return nativeClient.signIn(payload);
|
|
65
|
+
case "signOut": return nativeClient.signOut(payload);
|
|
66
|
+
case "isLoggedIn": return nativeClient.isLoggedIn(payload);
|
|
67
|
+
case "setupWallet": return nativeClient.setupWallet(payload);
|
|
68
|
+
case "getAddress": return nativeClient.getAddress(payload);
|
|
69
|
+
case "getAddresses": return nativeClient.getAddresses(payload);
|
|
70
|
+
case "getUserInfo": return nativeClient.getUserInfo(payload);
|
|
71
|
+
case "signMessage": return nativeClient.signMessage(payload);
|
|
72
|
+
case "signTypedData": return nativeClient.signTypedData(payload);
|
|
73
|
+
case "signTransaction": return nativeClient.signTransaction(payload);
|
|
74
|
+
case "sendTransaction": return nativeClient.sendTransaction(payload);
|
|
75
|
+
case "getBalance": return nativeClient.getBalance(payload);
|
|
76
|
+
case "getNonce": return nativeClient.getNonce(payload);
|
|
77
|
+
case "walletRpc": return nativeClient.walletRpc(payload);
|
|
78
|
+
case "openOAuth": return nativeClient.openOAuth(payload);
|
|
79
|
+
case "handleOAuthCallback":
|
|
80
|
+
if (nativeClient instanceof CROSSxAndroidNativeClient) {
|
|
81
|
+
return ((CROSSxAndroidNativeClient) nativeClient).handleCallbackUrl(payload.optString("url", ""));
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
default:
|
|
85
|
+
throw new UnsupportedOperationException("Android bridge method is not implemented: " + method);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private static String ok(Object result) throws JSONException {
|
|
90
|
+
JSONObject response = new JSONObject();
|
|
91
|
+
response.put("ok", true);
|
|
92
|
+
response.put("result", result);
|
|
93
|
+
return response.toString();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private static String error(String code, String message) {
|
|
97
|
+
try {
|
|
98
|
+
JSONObject err = new JSONObject();
|
|
99
|
+
err.put("code", code);
|
|
100
|
+
err.put("message", message);
|
|
101
|
+
JSONObject response = new JSONObject();
|
|
102
|
+
response.put("ok", false);
|
|
103
|
+
response.put("error", err);
|
|
104
|
+
return response.toString();
|
|
105
|
+
} catch (JSONException ignored) {
|
|
106
|
+
return "{\"ok\":false,\"error\":{\"code\":\"" + code + "\",\"message\":\"" + message + "\"}}";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Cocos Creator 2.x uses `org.cocos2dx.lib.*` (Cocos2d-x). We try the 2.x names
|
|
111
|
+
// first and keep the 3.x `com.cocos.lib.*` names as a fallback for newer host
|
|
112
|
+
// integrations that may have upgraded.
|
|
113
|
+
private static boolean dispatchDeepLinkToJs(String url) {
|
|
114
|
+
final String quotedUrl = JSONObject.quote(url).replace("
", "\\u2028").replace("
", "\\u2029");
|
|
115
|
+
final String script = "(function(u){var g=globalThis;if(g.CROSSxCocosHandleDeepLink){g.CROSSxCocosHandleDeepLink(u);}else{(g.CROSSxCocosPendingDeepLinks||(g.CROSSxCocosPendingDeepLinks=[])).push(u);}})(" + quotedUrl + ");";
|
|
116
|
+
Runnable task = new Runnable() {
|
|
117
|
+
@Override
|
|
118
|
+
public void run() {
|
|
119
|
+
evalString(script);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Cocos Creator 2.x (Cocos2d-x legacy)
|
|
124
|
+
try {
|
|
125
|
+
Class<?> helper = Class.forName("org.cocos2dx.lib.CocosHelper");
|
|
126
|
+
helper.getMethod("runOnGameThread", Runnable.class).invoke(null, task);
|
|
127
|
+
return true;
|
|
128
|
+
} catch (Exception ignored) {}
|
|
129
|
+
// Cocos Creator 3.x
|
|
130
|
+
try {
|
|
131
|
+
Class<?> helper = Class.forName("com.cocos.lib.CocosHelper");
|
|
132
|
+
helper.getMethod("runOnGameThread", Runnable.class).invoke(null, task);
|
|
133
|
+
return true;
|
|
134
|
+
} catch (Exception ignored) {}
|
|
135
|
+
return evalString(script);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static boolean evalString(String script) {
|
|
139
|
+
// Cocos Creator 2.x (Cocos2d-x legacy, lib package)
|
|
140
|
+
try {
|
|
141
|
+
Class<?> bridge = Class.forName("org.cocos2dx.lib.CocosJavascriptJavaBridge");
|
|
142
|
+
bridge.getMethod("evalString", String.class).invoke(null, script);
|
|
143
|
+
return true;
|
|
144
|
+
} catch (Exception ignored) {}
|
|
145
|
+
// Cocos Creator 2.x (Cocos2d-x legacy, javascript package)
|
|
146
|
+
try {
|
|
147
|
+
Class<?> bridge = Class.forName("org.cocos2dx.javascript.CocosJavascriptJavaBridge");
|
|
148
|
+
bridge.getMethod("evalString", String.class).invoke(null, script);
|
|
149
|
+
return true;
|
|
150
|
+
} catch (Exception ignored) {}
|
|
151
|
+
// Cocos Creator 3.x
|
|
152
|
+
try {
|
|
153
|
+
Class<?> bridge = Class.forName("com.cocos.lib.CocosJavascriptJavaBridge");
|
|
154
|
+
bridge.getMethod("evalString", String.class).invoke(null, script);
|
|
155
|
+
return true;
|
|
156
|
+
} catch (Exception ignored) {}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import org.json.JSONObject;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Native implementation boundary for the Cocos JSB bridge.
|
|
7
|
+
*
|
|
8
|
+
* The SDK package owns the bridge contract. The platform implementation can be
|
|
9
|
+
* backed by the CROSSx Android SDK, a gateway client, or a test double.
|
|
10
|
+
*/
|
|
11
|
+
public interface CROSSxCocosNativeClient {
|
|
12
|
+
Object initialize(JSONObject payload) throws Exception;
|
|
13
|
+
Object signIn(JSONObject payload) throws Exception;
|
|
14
|
+
Object signOut(JSONObject payload) throws Exception;
|
|
15
|
+
Object isLoggedIn(JSONObject payload) throws Exception;
|
|
16
|
+
Object setupWallet(JSONObject payload) throws Exception;
|
|
17
|
+
Object getAddress(JSONObject payload) throws Exception;
|
|
18
|
+
Object getAddresses(JSONObject payload) throws Exception;
|
|
19
|
+
Object getUserInfo(JSONObject payload) throws Exception;
|
|
20
|
+
Object signMessage(JSONObject payload) throws Exception;
|
|
21
|
+
Object signTypedData(JSONObject payload) throws Exception;
|
|
22
|
+
Object signTransaction(JSONObject payload) throws Exception;
|
|
23
|
+
Object sendTransaction(JSONObject payload) throws Exception;
|
|
24
|
+
Object getBalance(JSONObject payload) throws Exception;
|
|
25
|
+
Object getNonce(JSONObject payload) throws Exception;
|
|
26
|
+
Object walletRpc(JSONObject payload) throws Exception;
|
|
27
|
+
Object openOAuth(JSONObject payload) throws Exception;
|
|
28
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import io.crosstoken.crossxwebkit.ActivityProvider
|
|
6
|
+
import io.crosstoken.crossxwebkit.CustomTabsWebAuthAdapter
|
|
7
|
+
import kotlinx.coroutines.CoroutineScope
|
|
8
|
+
import kotlinx.coroutines.Dispatchers
|
|
9
|
+
import kotlinx.coroutines.Job
|
|
10
|
+
import kotlinx.coroutines.launch
|
|
11
|
+
|
|
12
|
+
object CROSSxOAuthBridge {
|
|
13
|
+
private const val TAG = "CROSSxOAuthBridge"
|
|
14
|
+
private var webAuth: CustomTabsWebAuthAdapter? = null
|
|
15
|
+
private var authJob: Job? = null
|
|
16
|
+
|
|
17
|
+
@JvmStatic
|
|
18
|
+
fun startOAuthFlow(authUrl: String, callbackScheme: String) {
|
|
19
|
+
val activity = CROSSxCocosActivityProvider.currentActivity()
|
|
20
|
+
if (activity == null) {
|
|
21
|
+
Log.e(TAG, "No current activity available")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
authJob?.cancel()
|
|
26
|
+
webAuth?.cancel()
|
|
27
|
+
webAuth = CustomTabsWebAuthAdapter(
|
|
28
|
+
activityProvider = object : ActivityProvider {
|
|
29
|
+
override fun currentActivity(): Activity? = CROSSxCocosActivityProvider.currentActivity()
|
|
30
|
+
},
|
|
31
|
+
fallbackContext = activity.applicationContext,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
authJob = CoroutineScope(Dispatchers.Main).launch {
|
|
35
|
+
try {
|
|
36
|
+
webAuth?.start(
|
|
37
|
+
url = authUrl,
|
|
38
|
+
callbackScheme = callbackScheme,
|
|
39
|
+
prefersEphemeralSession = false,
|
|
40
|
+
)
|
|
41
|
+
} catch (e: Exception) {
|
|
42
|
+
Log.e(TAG, "OAuth flow failed: ${e.message}", e)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@JvmStatic
|
|
48
|
+
fun handleCallbackUrl(url: String): Boolean {
|
|
49
|
+
return webAuth?.handleCallbackUrl(url) ?: false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@JvmStatic
|
|
53
|
+
fun cancelFlow() {
|
|
54
|
+
authJob?.cancel()
|
|
55
|
+
webAuth?.cancel()
|
|
56
|
+
webAuth = null
|
|
57
|
+
authJob = null
|
|
58
|
+
}
|
|
59
|
+
}
|
package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/PendingCROSSxCocosNativeClient.java
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
package com.crossx.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import org.json.JSONObject;
|
|
4
|
+
|
|
5
|
+
class PendingCROSSxCocosNativeClient implements CROSSxCocosNativeClient {
|
|
6
|
+
protected Object pending(String method) {
|
|
7
|
+
throw new UnsupportedOperationException("Android native SDK wiring is pending for method: " + method);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@Override
|
|
11
|
+
public Object initialize(JSONObject payload) throws Exception {
|
|
12
|
+
return JSONObject.NULL;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Override
|
|
16
|
+
public Object isLoggedIn(JSONObject payload) throws Exception {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override public Object signIn(JSONObject payload) throws Exception { return pending("signIn"); }
|
|
21
|
+
@Override public Object signOut(JSONObject payload) throws Exception { return pending("signOut"); }
|
|
22
|
+
@Override public Object setupWallet(JSONObject payload) throws Exception { return pending("setupWallet"); }
|
|
23
|
+
@Override public Object getAddress(JSONObject payload) throws Exception { return pending("getAddress"); }
|
|
24
|
+
@Override public Object getAddresses(JSONObject payload) throws Exception { return pending("getAddresses"); }
|
|
25
|
+
@Override public Object getUserInfo(JSONObject payload) throws Exception { return pending("getUserInfo"); }
|
|
26
|
+
@Override public Object signMessage(JSONObject payload) throws Exception { return pending("signMessage"); }
|
|
27
|
+
@Override public Object signTypedData(JSONObject payload) throws Exception { return pending("signTypedData"); }
|
|
28
|
+
@Override public Object signTransaction(JSONObject payload) throws Exception { return pending("signTransaction"); }
|
|
29
|
+
@Override public Object sendTransaction(JSONObject payload) throws Exception { return pending("sendTransaction"); }
|
|
30
|
+
@Override public Object getBalance(JSONObject payload) throws Exception { return pending("getBalance"); }
|
|
31
|
+
@Override public Object getNonce(JSONObject payload) throws Exception { return pending("getNonce"); }
|
|
32
|
+
@Override public Object walletRpc(JSONObject payload) throws Exception { return pending("walletRpc"); }
|
|
33
|
+
@Override public Object openOAuth(JSONObject payload) throws Exception { return pending("openOAuth"); }
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <AppKit/AppKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auto-installed via ObjC `+load`. Forwards macOS custom-URL-scheme events
|
|
8
|
+
* (delivered by the OS through `NSAppleEventManager` with the
|
|
9
|
+
* `kAEGetURL` Apple Event) to the JS runtime as
|
|
10
|
+
* `globalThis.CROSSxCocosHandleDeepLink(url)`. Pending URLs are queued
|
|
11
|
+
* until the script engine is ready, so the cold-launch deep link that
|
|
12
|
+
* triggered app activation is delivered as soon as the JS bridge attaches
|
|
13
|
+
* its handler.
|
|
14
|
+
*
|
|
15
|
+
* dApps don't need to call anything: simply having this source file
|
|
16
|
+
* compiled into the Cocos macOS target is enough.
|
|
17
|
+
*/
|
|
18
|
+
@interface CROSSxMacDeepLinkHandler : NSObject
|
|
19
|
+
|
|
20
|
+
+ (instancetype)shared;
|
|
21
|
+
+ (void)install;
|
|
22
|
+
|
|
23
|
+
@end
|
|
24
|
+
|
|
25
|
+
NS_ASSUME_NONNULL_END
|