diff --git a/.github/workflows/bob.yml b/.github/workflows/bob.yml new file mode 100644 index 0000000..e411a9c --- /dev/null +++ b/.github/workflows/bob.yml @@ -0,0 +1,76 @@ +name: Build with bob + +on: + push: + pull_request_target: + schedule: + # nightly at 05:00 on the 1st and 15th + - cron: 0 5 1,15 * * + +env: + VERSION_FILENAME: 'info.json' + BUILD_SERVER: 'https://build.defold.com' + +jobs: + build_with_bob: + strategy: + matrix: + platform: [armv7-android, x86_64-linux, x86_64-win32, x86-win32, js-web] + runs-on: ubuntu-latest + + name: Build + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '11.0.2' + + - name: Get Defold version + run: | + TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'` + echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV + echo "Found version ${TMPVAR}" + + - name: Download bob.jar + run: | + wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar + java -jar bob.jar --version + + - name: Resolve libraries + run: java -jar bob.jar resolve --email a@b.com --auth 123456 + - name: Build + run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}} + - name: Bundle + run: java -jar bob.jar --platform=${{ matrix.platform }} bundle + + # macOS is not technically needed for building, but we want to test bundling as well, since we're also testing the manifest merging + build_with_bob_macos: + strategy: + matrix: + platform: [armv7-darwin, x86_64-darwin] + runs-on: macOS-latest + + name: Build + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '11.0.2' + + - name: Get Defold version + run: | + TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'` + echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV + echo "Found version ${TMPVAR}" + + - name: Download bob.jar + run: | + wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar + java -jar bob.jar --version + + - name: Resolve libraries + run: java -jar bob.jar resolve --email a@b.com --auth 123456 + - name: Build + run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}} + - name: Bundle + run: java -jar bob.jar --platform=${{ matrix.platform }} bundle diff --git a/README.md b/README.md index 503fbff..401ea6f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Actions Status Alpha](https://github.com/defold/extension-iap/actions/workflows/bob.yml/badge.svg)](https://github.com/defold/extension-iap/actions) + # In-app purchase extension for Defold Defold [native extension](https://www.defold.com/manuals/extensions/) which provides access to In-app purchase functionality on iOS, Android (Google Play and Amazon) and Facebook Canvas platforms. diff --git a/extension-iap/src/iap_android.cpp b/extension-iap/src/iap_android.cpp index 5a8e5a8..08978a0 100644 --- a/extension-iap/src/iap_android.cpp +++ b/extension-iap/src/iap_android.cpp @@ -30,7 +30,6 @@ struct IAP m_autoFinishTransactions = true; m_ProviderId = PROVIDER_ID_GOOGLE; } - int m_InitCount; bool m_autoFinishTransactions; int m_ProviderId; @@ -399,59 +398,54 @@ static void HandlePurchaseResult(const IAPCommand* cmd) static dmExtension::Result InitializeIAP(dmExtension::Params* params) { - // TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization" - // Extend extension functionality with per system initalization? - if (g_IAP.m_InitCount == 0) { - IAP_Queue_Create(&g_IAP.m_CommandQueue); + IAP_Queue_Create(&g_IAP.m_CommandQueue); - g_IAP.m_autoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1; + g_IAP.m_autoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1; - JNIEnv* env = Attach(); + JNIEnv* env = Attach(); - jclass activity_class = env->FindClass("android/app/NativeActivity"); - jmethodID get_class_loader = env->GetMethodID(activity_class,"getClassLoader", "()Ljava/lang/ClassLoader;"); - jobject cls = env->CallObjectMethod(dmGraphics::GetNativeAndroidActivity(), get_class_loader); - jclass class_loader = env->FindClass("java/lang/ClassLoader"); - jmethodID find_class = env->GetMethodID(class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + jclass activity_class = env->FindClass("android/app/NativeActivity"); + jmethodID get_class_loader = env->GetMethodID(activity_class,"getClassLoader", "()Ljava/lang/ClassLoader;"); + jobject cls = env->CallObjectMethod(dmGraphics::GetNativeAndroidActivity(), get_class_loader); + jclass class_loader = env->FindClass("java/lang/ClassLoader"); + jmethodID find_class = env->GetMethodID(class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - const char* provider = dmConfigFile::GetString(params->m_ConfigFile, "android.iap_provider", "GooglePlay"); - const char* class_name = "com.defold.iap.IapGooglePlay"; + const char* provider = dmConfigFile::GetString(params->m_ConfigFile, "android.iap_provider", "GooglePlay"); + const char* class_name = "com.defold.iap.IapGooglePlay"; - g_IAP.m_ProviderId = PROVIDER_ID_GOOGLE; - if (!strcmp(provider, "Amazon")) { - g_IAP.m_ProviderId = PROVIDER_ID_AMAZON; - class_name = "com.defold.iap.IapAmazon"; - } - else if (strcmp(provider, "GooglePlay")) { - dmLogWarning("Unknown IAP provider name [%s], defaulting to GooglePlay", provider); - } - - jstring str_class_name = env->NewStringUTF(class_name); - - jclass iap_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name); - env->DeleteLocalRef(str_class_name); - - str_class_name = env->NewStringUTF("com.defold.iap.IapJNI"); - jclass iap_jni_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name); - env->DeleteLocalRef(str_class_name); - - g_IAP.m_List = env->GetMethodID(iap_class, "listItems", "(Ljava/lang/String;Lcom/defold/iap/IListProductsListener;J)V"); - g_IAP.m_Buy = env->GetMethodID(iap_class, "buy", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); - g_IAP.m_Restore = env->GetMethodID(iap_class, "restore", "(Lcom/defold/iap/IPurchaseListener;)V"); - g_IAP.m_Stop = env->GetMethodID(iap_class, "stop", "()V"); - g_IAP.m_ProcessPendingConsumables = env->GetMethodID(iap_class, "processPendingConsumables", "(Lcom/defold/iap/IPurchaseListener;)V"); - g_IAP.m_FinishTransaction = env->GetMethodID(iap_class, "finishTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); - g_IAP.m_AcknowledgeTransaction = env->GetMethodID(iap_class, "acknowledgeTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); - - jmethodID jni_constructor = env->GetMethodID(iap_class, "", "(Landroid/app/Activity;Z)V"); - g_IAP.m_IAP = env->NewGlobalRef(env->NewObject(iap_class, jni_constructor, dmGraphics::GetNativeAndroidActivity(), g_IAP.m_autoFinishTransactions)); - - jni_constructor = env->GetMethodID(iap_jni_class, "", "()V"); - g_IAP.m_IAPJNI = env->NewGlobalRef(env->NewObject(iap_jni_class, jni_constructor)); - - Detach(); + g_IAP.m_ProviderId = PROVIDER_ID_GOOGLE; + if (!strcmp(provider, "Amazon")) { + g_IAP.m_ProviderId = PROVIDER_ID_AMAZON; + class_name = "com.defold.iap.IapAmazon"; } - g_IAP.m_InitCount++; + else if (strcmp(provider, "GooglePlay")) { + dmLogWarning("Unknown IAP provider name [%s], defaulting to GooglePlay", provider); + } + + jstring str_class_name = env->NewStringUTF(class_name); + + jclass iap_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name); + env->DeleteLocalRef(str_class_name); + + str_class_name = env->NewStringUTF("com.defold.iap.IapJNI"); + jclass iap_jni_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name); + env->DeleteLocalRef(str_class_name); + + g_IAP.m_List = env->GetMethodID(iap_class, "listItems", "(Ljava/lang/String;Lcom/defold/iap/IListProductsListener;J)V"); + g_IAP.m_Buy = env->GetMethodID(iap_class, "buy", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); + g_IAP.m_Restore = env->GetMethodID(iap_class, "restore", "(Lcom/defold/iap/IPurchaseListener;)V"); + g_IAP.m_Stop = env->GetMethodID(iap_class, "stop", "()V"); + g_IAP.m_ProcessPendingConsumables = env->GetMethodID(iap_class, "processPendingConsumables", "(Lcom/defold/iap/IPurchaseListener;)V"); + g_IAP.m_FinishTransaction = env->GetMethodID(iap_class, "finishTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); + g_IAP.m_AcknowledgeTransaction = env->GetMethodID(iap_class, "acknowledgeTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); + + jmethodID jni_constructor = env->GetMethodID(iap_class, "", "(Landroid/app/Activity;Z)V"); + g_IAP.m_IAP = env->NewGlobalRef(env->NewObject(iap_class, jni_constructor, dmGraphics::GetNativeAndroidActivity(), g_IAP.m_autoFinishTransactions)); + + jni_constructor = env->GetMethodID(iap_jni_class, "", "()V"); + g_IAP.m_IAPJNI = env->NewGlobalRef(env->NewObject(iap_jni_class, jni_constructor)); + + Detach(); lua_State*L = params->m_L; int top = lua_gettop(L); @@ -494,21 +488,18 @@ static dmExtension::Result UpdateIAP(dmExtension::Params* params) static dmExtension::Result FinalizeIAP(dmExtension::Params* params) { IAP_Queue_Destroy(&g_IAP.m_CommandQueue); - --g_IAP.m_InitCount; if (params->m_L == dmScript::GetCallbackLuaContext(g_IAP.m_Listener)) { dmScript::DestroyCallback(g_IAP.m_Listener); g_IAP.m_Listener = 0; } - if (g_IAP.m_InitCount == 0) { - JNIEnv* env = Attach(); - env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Stop); - env->DeleteGlobalRef(g_IAP.m_IAP); - env->DeleteGlobalRef(g_IAP.m_IAPJNI); - Detach(); - g_IAP.m_IAP = NULL; - } + JNIEnv* env = Attach(); + env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Stop); + env->DeleteGlobalRef(g_IAP.m_IAP); + env->DeleteGlobalRef(g_IAP.m_IAPJNI); + Detach(); + g_IAP.m_IAP = NULL; return dmExtension::RESULT_OK; } diff --git a/extension-iap/src/iap_ios.mm b/extension-iap/src/iap_ios.mm index 0ec8bef..d4f41fe 100644 --- a/extension-iap/src/iap_ios.mm +++ b/extension-iap/src/iap_ios.mm @@ -24,7 +24,7 @@ struct IAP memset(this, 0, sizeof(*this)); m_AutoFinishTransactions = true; } - int m_InitCount; + int m_Version; bool m_AutoFinishTransactions; NSMutableDictionary* m_PendingTransactions; dmScript::LuaCallbackInfo* m_Listener; @@ -103,11 +103,17 @@ static void IAP_FreeTransaction(IAPTransaction* transaction) @interface SKProductsRequestDelegate : NSObject @property dmScript::LuaCallbackInfo* m_Callback; @property (assign) SKProductsRequest* m_Request; + @property int m_Version; @end @implementation SKProductsRequestDelegate - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ + if (self.m_Version != g_IAP.m_Version) { + dmLogWarning("Received products but the extension has been restarted") + return; + } + if (!dmScript::IsCallbackValid(self.m_Callback)) { dmLogError("No callback set"); return; @@ -162,7 +168,7 @@ static void HandleProductResult(IAPCommand* cmd) IAPResponse* response = (IAPResponse*)cmd->m_Data; - lua_State* L = dmScript::GetCallbackLuaContext(g_IAP.m_Listener); + lua_State* L = dmScript::GetCallbackLuaContext(cmd->m_Callback); int top = lua_gettop(L); if (!dmScript::SetupCallback(cmd->m_Callback)) @@ -409,6 +415,7 @@ static int IAP_List(lua_State* L) delegate.m_Callback = dmScript::CreateCallback(L, 2); delegate.m_Request = products_request; + delegate.m_Version = g_IAP.m_Version; products_request.delegate = delegate; [products_request start]; @@ -529,13 +536,9 @@ static const luaL_reg IAP_methods[] = static dmExtension::Result InitializeIAP(dmExtension::Params* params) { - // TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization" - // Extend extension functionality with per system initalization? - if (g_IAP.m_InitCount == 0) { - g_IAP.m_AutoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1; - g_IAP.m_PendingTransactions = [[NSMutableDictionary alloc]initWithCapacity:2]; - } - g_IAP.m_InitCount++; + g_IAP.m_AutoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1; + g_IAP.m_PendingTransactions = [[NSMutableDictionary alloc]initWithCapacity:2]; + g_IAP.m_Version++; IAP_Queue_Create(&g_IAP.m_CommandQueue); IAP_Queue_Create(&g_IAP.m_ObservableQueue); @@ -590,26 +593,18 @@ static dmExtension::Result UpdateIAP(dmExtension::Params* params) static dmExtension::Result FinalizeIAP(dmExtension::Params* params) { - --g_IAP.m_InitCount; + dmScript::DestroyCallback(g_IAP.m_Listener); + g_IAP.m_Listener = 0; - // TODO: Should we support one listener per lua-state? - // Or just use a single lua-state...? - if (params->m_L == dmScript::GetCallbackLuaContext(g_IAP.m_Listener)) { - dmScript::DestroyCallback(g_IAP.m_Listener); - g_IAP.m_Listener = 0; + if (g_IAP.m_PendingTransactions) { + [g_IAP.m_PendingTransactions release]; + g_IAP.m_PendingTransactions = 0; } - if (g_IAP.m_InitCount == 0) { - if (g_IAP.m_PendingTransactions) { - [g_IAP.m_PendingTransactions release]; - g_IAP.m_PendingTransactions = 0; - } - - if (g_IAP.m_Observer) { - [[SKPaymentQueue defaultQueue] removeTransactionObserver: g_IAP.m_Observer]; - [g_IAP.m_Observer release]; - g_IAP.m_Observer = 0; - } + if (g_IAP.m_Observer) { + [[SKPaymentQueue defaultQueue] removeTransactionObserver: g_IAP.m_Observer]; + [g_IAP.m_Observer release]; + g_IAP.m_Observer = 0; } IAP_Queue_Destroy(&g_IAP.m_CommandQueue);