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.
Files changed (71) hide show
  1. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxAndroidNativeClient.java +103 -0
  2. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxAndroidSecureStorage.java +138 -0
  3. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosActivityProvider.java +33 -0
  4. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosBridge.java +159 -0
  5. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosBridgeInstaller.java +9 -0
  6. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxCocosNativeClient.java +28 -0
  7. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/CROSSxOAuthBridge.kt +59 -0
  8. package/Plugins/Android/src/main/java/com/crossx/sdk/cocos/PendingCROSSxCocosNativeClient.java +34 -0
  9. package/Plugins/Mac/Classes/CROSSxMacDeepLinkHandler.h +25 -0
  10. package/Plugins/Mac/Classes/CROSSxMacDeepLinkHandler.mm +229 -0
  11. package/Plugins/README.md +117 -0
  12. package/Plugins/iOS/Classes/CROSSxCocosBridge.h +40 -0
  13. package/Plugins/iOS/Classes/CROSSxCocosBridge.mm +176 -0
  14. package/Plugins/iOS/Classes/CROSSxCocosBridgeInstaller.h +7 -0
  15. package/Plugins/iOS/Classes/CROSSxCocosBridgeInstaller.mm +11 -0
  16. package/Plugins/iOS/Classes/CROSSxCocosOAuthWrapper.swift +58 -0
  17. package/Plugins/iOS/Classes/CROSSxIOSNativeClient.h +5 -0
  18. package/Plugins/iOS/Classes/CROSSxIOSNativeClient.mm +129 -0
  19. package/Plugins/iOS/Classes/CROSSxIOSSecureStorage.h +22 -0
  20. package/Plugins/iOS/Classes/CROSSxIOSSecureStorage.mm +67 -0
  21. package/Runtime/Adapters/DeepLink/CocosDeepLinkDispatcher.ts +98 -0
  22. package/Runtime/Adapters/DeepLink/CocosWebkitLifecycleDispatcher.ts +73 -0
  23. package/Runtime/Adapters/Gateway/GatewayWalletAdapter.ts +879 -0
  24. package/Runtime/Adapters/Mock/MockWalletAdapter.ts +137 -0
  25. package/Runtime/Adapters/Storage/CocosStorageAdapter.ts +153 -0
  26. package/Runtime/Adapters/Web/CrossySdkJsGlobal.ts +80 -0
  27. package/Runtime/Adapters/Web/WebOAuthAdapter.ts +13 -0
  28. package/Runtime/Adapters/Web/WebWalletAdapter.ts +122 -0
  29. package/Runtime/Adapters/Windows/WindowsOAuthAdapter.ts +60 -0
  30. package/Runtime/Adapters/Windows/WindowsWalletAdapter.ts +23 -0
  31. package/Runtime/Adapters/Windows/src/WindowsLoopbackCallbackAdapter.ts +254 -0
  32. package/Runtime/Core/Endpoints.ts +44 -0
  33. package/Runtime/Core/I18n/messages.ts +294 -0
  34. package/Runtime/Core/Ports/CROSSxSdkPorts.ts +15 -0
  35. package/Runtime/Core/Ports/IDeepLinkPort.ts +7 -0
  36. package/Runtime/Core/Ports/INativeBridgePort.ts +30 -0
  37. package/Runtime/Core/Ports/IOAuthPort.ts +5 -0
  38. package/Runtime/Core/Ports/IStoragePort.ts +6 -0
  39. package/Runtime/Core/Ports/IWalletPort.ts +40 -0
  40. package/Runtime/Core/Ports/NativeBridgeMethods.ts +75 -0
  41. package/Runtime/Core/Types/CROSSxSdkTypes.ts +313 -0
  42. package/Runtime/Core/UseCases/CROSSxSdkUseCases.ts +6 -0
  43. package/Runtime/Core/UseCases/GetAddressUseCase.ts +14 -0
  44. package/Runtime/Core/UseCases/LoginUseCase.ts +10 -0
  45. package/Runtime/Core/UseCases/LogoutUseCase.ts +9 -0
  46. package/Runtime/Core/UseCases/SendTransactionUseCase.ts +14 -0
  47. package/Runtime/Core/UseCases/SetupWalletUseCase.ts +10 -0
  48. package/Runtime/Core/UseCases/SignUseCase.ts +22 -0
  49. package/Runtime/Core/Utils/crosspay.ts +223 -0
  50. package/Runtime/Core/Utils/hexQuantity.ts +73 -0
  51. package/Runtime/Core/Utils/oauth.ts +179 -0
  52. package/Runtime/Core/Utils/schemes.ts +9 -0
  53. package/Runtime/SDK/CROSSxCocosSDK.ts +1734 -0
  54. package/Runtime/SDK/CROSSxCocosSDKFactory.ts +119 -0
  55. package/Runtime/SDK/CROSSxSdk.ts +2 -0
  56. package/Runtime/UI/CROSSxSdkUI.ts +15 -0
  57. package/Runtime/UI/CocosDesignTokens.ts +184 -0
  58. package/Runtime/UI/CocosSdkModal.ts +3656 -0
  59. package/Runtime/UI/SDKUIProvider.ts +167 -0
  60. package/Runtime/types.ts +62 -0
  61. package/editor/dist/build-plugin.js +60 -0
  62. package/editor/dist/build-processors/android-post-process.js +516 -0
  63. package/editor/dist/build-processors/file-copy.js +79 -0
  64. package/editor/dist/build-processors/index.js +22 -0
  65. package/editor/dist/build-processors/ios-post-process.js +783 -0
  66. package/editor/dist/build-processors/macos-post-process.js +382 -0
  67. package/editor/dist/build-processors/native-build-post-process.js +453 -0
  68. package/editor/dist/build-processors/native-dependencies.js +76 -0
  69. package/editor/dist/build-settings.js +213 -0
  70. package/editor/dist/main.js +44 -0
  71. 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,9 @@
1
+ package com.crossx.sdk.cocos;
2
+
3
+ public final class CROSSxCocosBridgeInstaller {
4
+ private CROSSxCocosBridgeInstaller() {}
5
+
6
+ public static void installDefaultClients() {
7
+ CROSSxCocosBridge.setNativeClient(new CROSSxAndroidNativeClient());
8
+ }
9
+ }
@@ -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
+ }
@@ -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