Repository: rakutentech/js-miniapp
Pull request #313: LDESK-17720 miniapp integration
Author: login: Toby-SingLeungHo
Created: 2025-05-19T05:32:35Z | Merged: 2025-05-20T11:13:39Z

# Description
Using js-miniapp SDK in Electron based applications.

## Links
[LDESK-17720](https://jira.rakuten-it.com/jira/browse/LDESK-17720) - Provide new communication bridge for Electron

# Checklist
- [x] I have completed all steps in the [Contribution Checklist](../blob/master.github/CONTRIBUTING.md#checklist)



Diff:
diff --git a/js-miniapp-bridge/package.json b/js-miniapp-bridge/package.json
index a08c7ea1d..d7a7dbe73 100644
--- a/js-miniapp-bridge/package.json
+++ b/js-miniapp-bridge/package.json
@@ -21,14 +21,16 @@
     "source-map-support": "^0.5.21",
     "ts-node": "^8.8.1",
     "tslint": "^6.1.0",
-    "typescript": "^4.9.4"
+    "typescript": "^4.9.4",
+    "electron": "13.6.1"
   },
   "scripts": {
     "lint": "gts check",
     "clean": "gts clean",
     "browserify-android": "browserify ./build/src/android/android-bridge.js > build/src/android/bridge.js && browserify ./build/src/android/android-log.js > build/src/android/log.js",
     "browserify-ios": "browserify ./build/src/ios/ios-bridge.js > build/src/ios/bridge.js && browserify ./build/src/ios/ios-log.js > build/src/ios/log.js",
-    "compile": "tsc -p . && npm run browserify-android  && npm run browserify-ios",
+    "browserify-electron": "browserify ./build/src/electron/electron-bridge.js > build/src/electron/bridge.js && browserify ./build/src/electron/electron-log.js > build/src/electron/log.js",
+    "compile": "tsc -p . && npm run browserify-android  && npm run browserify-ios  && npm run browserify-electron",
     "prebuild": "npm run clean && npm run lint",
     "buildBridge": "npm run lint && npm run compile && npm run test",
     "fix": "gts fix",
diff --git a/js-miniapp-bridge/src/android/android-bridge.ts b/js-miniapp-bridge/src/android/android-bridge.ts
index e6925c6fa..426339503 100644
--- a/js-miniapp-bridge/src/android/android-bridge.ts
+++ b/js-miniapp-bridge/src/android/android-bridge.ts
@@ -3,11 +3,12 @@ import {
   MiniAppBridge,
   Callback,
   mabMessageQueue,
+  cryptoRandom,
 } from '../common-bridge';
 import { Platform } from '../types/platform';
 
 /* tslint:disable:no-any */
-let uniqueId = Math.random();
+let uniqueId = cryptoRandom();
 
 class AndroidExecutor implements PlatformExecutor {
   execEvents(event) {
diff --git a/js-miniapp-bridge/src/android/android-log.ts b/js-miniapp-bridge/src/android/android-log.ts
index d93dfebf8..ed6e1f7cc 100644
--- a/js-miniapp-bridge/src/android/android-log.ts
+++ b/js-miniapp-bridge/src/android/android-log.ts
@@ -5,13 +5,11 @@ class AndroidSDKLogger implements PlatformLogger {
   log(emoji, type, args) {
     (window as any).MiniAppAndroid.postMessage(
       `${emoji} console.${type}: ${Object.values(args)
-        .map(v =>
-          typeof v === 'undefined'
-            ? 'undefined'
-            : typeof v === 'object'
-            ? JSON.stringify(v)
-            : v.toString()
-        )
+        .map(v => {
+          if (typeof v === 'undefined') return 'undefined';
+          else if (typeof v === 'object') return JSON.stringify(v);
+          else return v.toString();
+        })
         .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
         .join(', ')}`
     );
diff --git a/js-miniapp-bridge/src/common-bridge.ts b/js-miniapp-bridge/src/common-bridge.ts
index 1ee713fe7..d677b6f63 100644
--- a/js-miniapp-bridge/src/common-bridge.ts
+++ b/js-miniapp-bridge/src/common-bridge.ts
@@ -3,6 +3,7 @@
 /**
  * Bridge for communicating with Mini App
  */
+const crypto = require('crypto');
 
 import { AdTypes } from './types/ad-types';
 import { Contact } from './types/contact';
@@ -46,6 +47,7 @@ import { WebViewConfigManager } from './modules/webview-config-manager';
 import { UtitlityManager } from './modules/utility-manager';
 import { LogType } from './types/log-type';
 import { EsimConfig } from './types/e-sim';
+import { Platform } from './types/platform';
 
 /** @internal */
 const mabMessageQueue: Callback[] = [];
@@ -176,9 +178,9 @@ export class MiniAppBridge {
     // and decoded here.
     let result = value;
     if (eventType === 'miniappreceivejsoninfo') {
-      if (this.platform === 'iOS') {
+      if (this.platform === Platform.IOS) {
         result = convertUnicodeCharacters(value);
-      } else {
+      } else if (this.platform === Platform.ANDROID) {
         result = convertUnicodeCharactersForAndroid(value);
       }
     }
@@ -409,10 +411,12 @@ export class MiniAppBridge {
         'getUserName',
         null,
         userName => {
-          let value;
-          if (this.platform === 'iOS') {
+          let value = userName;
+          if (this.platform === Platform.IOS) {
             value = convertUnicodeCharacters(userName);
-          } else {
+          } else if (this.platform === Platform.ANDROID) {
+            value = convertUnicodeCharactersForAndroid(userName);
+          } else if (this.platform !== Platform.ELECTRON) {
             value = convertUnicodeCharactersForAndroid(userName);
           }
           resolve(value);
@@ -1204,3 +1208,13 @@ export class MiniAppBridgeUtils {
     return false;
   }
 }
+
+/**
+ * @description Math.random is a security risk, crypto is used to generate strong pseudo numbers
+ * @returns random float number
+ */
+export function cryptoRandom() {
+  const buffer = crypto.randomBytes(4); // Generate 4 random bytes
+  const value = buffer.readUInt32LE(0);
+  return value / 0xffffffff;
+}
diff --git a/js-miniapp-bridge/src/electron/electron-bridge.ts b/js-miniapp-bridge/src/electron/electron-bridge.ts
new file mode 100644
index 000000000..ce1f47a2f
--- /dev/null
+++ b/js-miniapp-bridge/src/electron/electron-bridge.ts
@@ -0,0 +1,44 @@
+import {
+  PlatformExecutor,
+  MiniAppBridge,
+  Callback,
+  mabMessageQueue,
+  cryptoRandom,
+} from '../common-bridge'; // Adjust the import path as needed
+import { Platform } from '../types/platform'; // Adjust the import path as needed
+
+/* tslint:disable:no-any */
+let uniqueId = cryptoRandom();
+
+class ElectronExecutor implements PlatformExecutor {
+  execEvents(event: Event): void {
+    (window as any).dispatchEvent(event);
+  }
+
+  exec(
+    action: string,
+    param: any,
+    onSuccess: (value: string) => void,
+    onError: (error: string) => void
+  ): void {
+    const callback = {} as Callback;
+    callback.onSuccess = onSuccess;
+    callback.onError = onError;
+    callback.id = String(++uniqueId);
+    mabMessageQueue.unshift(callback);
+
+    (window as any).MiniAppElectron[action]({ param, id: callback.id })
+      .then(response => {
+        return callback.onSuccess(response);
+      })
+      .catch(error => {
+        return callback.onError(error);
+      });
+  }
+
+  getPlatform(): string {
+    return Platform.ELECTRON;
+  }
+}
+
+(window as any).MiniAppBridge = new MiniAppBridge(new ElectronExecutor());
diff --git a/js-miniapp-bridge/src/electron/electron-log.ts b/js-miniapp-bridge/src/electron/electron-log.ts
new file mode 100644
index 000000000..b577ab5d7
--- /dev/null
+++ b/js-miniapp-bridge/src/electron/electron-log.ts
@@ -0,0 +1,20 @@
+/* tslint:disable:no-any */
+import { MiniAppSDKLogger, PlatformLogger } from '../common-log';
+
+class ElectronSDKLogger implements PlatformLogger {
+  log(emoji, type, args) {
+    (window as any).MiniAppElectron.log(
+      `${emoji} console.${type}: ${Object.values(args)
+        .map(v => {
+          if (typeof v === 'undefined') return 'undefined';
+          else if (typeof v === 'object') return JSON.stringify(v);
+          else return v.toString();
+        })
+        .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
+        .join(', ')}`
+    );
+  }
+}
+
+const electronLogger = new ElectronSDKLogger();
+(window as any).MiniAppSDKLogger = new MiniAppSDKLogger(electronLogger);
diff --git a/js-miniapp-bridge/src/ios/ios-bridge.ts b/js-miniapp-bridge/src/ios/ios-bridge.ts
index b12ec28b1..294508a4f 100644
--- a/js-miniapp-bridge/src/ios/ios-bridge.ts
+++ b/js-miniapp-bridge/src/ios/ios-bridge.ts
@@ -3,11 +3,12 @@ import {
   MiniAppBridge,
   Callback,
   mabMessageQueue,
+  cryptoRandom,
 } from '../common-bridge';
 import { Platform } from '../types/platform';
 
 /* tslint:disable:no-any */
-let uniqueId = Math.random();
+let uniqueId = cryptoRandom();
 
 // tslint:disable-next-line: variable-name
 const GeolocationPositionError = {
diff --git a/js-miniapp-bridge/src/ios/ios-log.ts b/js-miniapp-bridge/src/ios/ios-log.ts
index 6fe0055b9..22e3c1836 100644
--- a/js-miniapp-bridge/src/ios/ios-log.ts
+++ b/js-miniapp-bridge/src/ios/ios-log.ts
@@ -5,13 +5,11 @@ class IOSSDKLogger implements PlatformLogger {
   log(emoji, type, args) {
     (window as any).webkit.messageHandlers.MiniAppLogging.postMessage(
       `${emoji} console.${type}: ${Object.values(args)
-        .map(v =>
-          typeof v === 'undefined'
-            ? 'undefined'
-            : typeof v === 'object'
-            ? JSON.stringify(v)
-            : v.toString()
-        )
+        .map(v => {
+          if (typeof v === 'undefined') return 'undefined';
+          else if (typeof v === 'object') return JSON.stringify(v);
+          else return v.toString();
+        })
         .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
         .join(', ')}`
     );
diff --git a/js-miniapp-bridge/src/types/platform.ts b/js-miniapp-bridge/src/types/platform.ts
index 7e1461481..8628e85cf 100644
--- a/js-miniapp-bridge/src/types/platform.ts
+++ b/js-miniapp-bridge/src/types/platform.ts
@@ -4,6 +4,7 @@
 export enum Platform {
   ANDROID = 'Android',
   IOS = 'iOS',
+  ELECTRON = 'Electron',
 }
 
 /** Device Build Types. */
diff --git a/js-miniapp-bridge/test/test.spec.ts b/js-miniapp-bridge/test/test.spec.ts
index d91ed04fa..c457ee219 100644
--- a/js-miniapp-bridge/test/test.spec.ts
+++ b/js-miniapp-bridge/test/test.spec.ts
@@ -1199,6 +1199,6 @@ function createCallback({
   return {
     onSuccess: onSuccess || (() => undefined),
     onError: onError || (() => undefined),
-    id: String(Math.random()),
+    id: String(Bridge.cryptoRandom()),
   };
 }


Reviews:
user: login: Climbatize
body: Looks very good to me!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T06:34:58Z
user: login: rleojoseph
body: Thanks for the changes @Toby-SingLeungHo. Looks good to me, just one comment.
Also, you may need update the documentation later on how we are supporting desktop bridge as well
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-20T00:43:05Z
user: login: Toby-SingLeungHo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T01:06:43Z
user: login: rleojoseph
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T05:59:57Z

Review comments:
diff_hunk: @@ -0,0 +1,22 @@
+/* tslint:disable:no-any */
+import { MiniAppSDKLogger, PlatformLogger } from '../common-log';
+
+class ElectronSDKLogger implements PlatformLogger {
+  log(emoji, type, args) {
+    (window as any).MiniAppElectron.log(
+      `${emoji} console.${type}: ${Object.values(args)
+        .map(v =>
+          typeof v === 'undefined'
+            ? 'undefined'
+            : typeof v === 'object'
+            ? JSON.stringify(v)
+            : v.toString()
+        )
+        .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
path: js-miniapp-bridge/src/electron/electron-log.ts
user: login: rleojoseph
body: Any reason for this limitation ? If there is more than 3000 chars you will truncate them ?
created_at: 2025-05-20T00:41:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,22 @@
+/* tslint:disable:no-any */
+import { MiniAppSDKLogger, PlatformLogger } from '../common-log';
+
+class ElectronSDKLogger implements PlatformLogger {
+  log(emoji, type, args) {
+    (window as any).MiniAppElectron.log(
+      `${emoji} console.${type}: ${Object.values(args)
+        .map(v =>
+          typeof v === 'undefined'
+            ? 'undefined'
+            : typeof v === 'object'
+            ? JSON.stringify(v)
+            : v.toString()
+        )
+        .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
path: js-miniapp-bridge/src/electron/electron-log.ts
user: login: Toby-SingLeungHo
body: This was done following Android and iOS's method, also saves some space for Desktop's log storage.
created_at: 2025-05-20T01:06:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+/* tslint:disable:no-any */
+import { MiniAppSDKLogger, PlatformLogger } from '../common-log';
+
+class ElectronSDKLogger implements PlatformLogger {
+  log(emoji, type, args) {
+    (window as any).MiniAppElectron.log(
+      `${emoji} console.${type}: ${Object.values(args)
+        .map(v =>
+          typeof v === 'undefined'
+            ? 'undefined'
+            : typeof v === 'object'
+            ? JSON.stringify(v)
+            : v.toString()
+        )
+        .map(v => v.substring(0, 3000)) // Limit msg to 3000 chars
path: js-miniapp-bridge/src/electron/electron-log.ts
user: login: rleojoseph
body: Understood.
created_at: 2025-05-20T05:59:57Z
author_association: COLLABORATOR

PR comments:
user: login: codecov-commenter
created_at: 2025-05-19T06:34:12Z
body: ## [Codecov](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech) Report
Attention: Patch coverage is `24.24242%` with `25 lines` in your changes missing coverage. Please review.
> Project coverage is 71.59%. Comparing base [(`becd253`)](https://app.codecov.io/gh/rakutentech/js-miniapp/commit/becd253313f76f2241f64fdf115f00c6015f7b32?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech) to head [(`4b7468e`)](https://app.codecov.io/gh/rakutentech/js-miniapp/commit/4b7468e89e498b31ce5d5aff2126aaa6fa55e136?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech).
> Report is 1 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech) | Patch % | Lines |
|---|---|---|
| [js-miniapp-bridge/src/electron/electron-bridge.ts](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?src=pr&el=tree&filepath=js-miniapp-bridge%2Fsrc%2Felectron%2Felectron-bridge.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech#diff-anMtbWluaWFwcC1icmlkZ2Uvc3JjL2VsZWN0cm9uL2VsZWN0cm9uLWJyaWRnZS50cw==) | 0.00% | [12 Missing :warning: ](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech) |
| [js-miniapp-bridge/src/electron/electron-log.ts](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?src=pr&el=tree&filepath=js-miniapp-bridge%2Fsrc%2Felectron%2Felectron-log.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech#diff-anMtbWluaWFwcC1icmlkZ2Uvc3JjL2VsZWN0cm9uL2VsZWN0cm9uLWxvZy50cw==) | 0.00% | [7 Missing :warning: ](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech) |
| [js-miniapp-bridge/src/common-bridge.ts](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?src=pr&el=tree&filepath=js-miniapp-bridge%2Fsrc%2Fcommon-bridge.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech#diff-anMtbWluaWFwcC1icmlkZ2Uvc3JjL2NvbW1vbi1icmlkZ2UudHM=) | 53.84% | [3 Missing and 3 partials :warning: ](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #313      +/-   ##
==========================================
- Coverage   72.92%   71.59%   -1.33%     
==========================================
  Files          36       38       +2     
  Lines        1071     1102      +31     
  Branches       69       74       +5     
==========================================
+ Hits          781      789       +8     
- Misses        262      283      +21     
- Partials       28       30       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/rakutentech/js-miniapp/pull/313?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=rakutentech).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-20T07:38:25Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=rakutentech_js-miniapp&pullRequest=313) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=rakutentech_js-miniapp&pullRequest=313&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=rakutentech_js-miniapp&pullRequest=313&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=rakutentech_js-miniapp&pullRequest=313&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=rakutentech_js-miniapp&pullRequest=313&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=rakutentech_js-miniapp&pullRequest=313&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=rakutentech_js-miniapp&pullRequest=313)


author_association: NONE