Skip to content

Commit 9b16625

Browse files
feat(game-bridge): enhance error diagnostics and consolidate build output (#2785)
1 parent 9ff1bde commit 9b16625

File tree

7 files changed

+878
-530
lines changed

7 files changed

+878
-530
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ examples/**/*
2020
packages/internal/generated-clients/src/
2121

2222
# put module specific ignore paths here
23+
packages/game-bridge/scripts/**/*.js

packages/auth/src/errors.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@ export function isAPIError(error: any): error is imx.APIError {
3636

3737
type AxiosLikeError = {
3838
response?: {
39+
status?: number;
3940
data?: unknown;
4041
};
42+
config?: {
43+
url?: string;
44+
baseURL?: string;
45+
method?: string;
46+
};
4147
};
4248

4349
const extractApiError = (error: unknown): imx.APIError | undefined => {
@@ -59,6 +65,23 @@ const extractApiError = (error: unknown): imx.APIError | undefined => {
5965
return undefined;
6066
};
6167

68+
const appendHttpDebugInfo = (message: string, error: unknown): string => {
69+
const e = error as AxiosLikeError;
70+
const status = e?.response?.status;
71+
const url = e?.config?.url;
72+
const baseURL = e?.config?.baseURL;
73+
const fullUrl = (
74+
typeof url === 'string' && typeof baseURL === 'string' && !/^https?:\/\//i.test(url)
75+
? `${baseURL}${url}`
76+
: url
77+
);
78+
79+
if (status == null && fullUrl == null) return message;
80+
if (message.includes('[httpStatus=')) return message;
81+
82+
return `${message} [httpStatus=${status ?? 'unknown'} url=${fullUrl ?? 'unknown'}]`;
83+
};
84+
6285
export class PassportError extends Error {
6386
public type: PassportErrorType;
6487

@@ -88,6 +111,11 @@ export const withPassportError = async <T>(
88111
errorMessage = (error as Error).message;
89112
}
90113

114+
// Debug aid: preserve which HTTP endpoint/status failed for IMX offchain registration.
115+
if (customErrorType === PassportErrorType.USER_REGISTRATION_ERROR) {
116+
errorMessage = appendHttpDebugInfo(errorMessage, error);
117+
}
118+
91119
throw new PassportError(errorMessage, customErrorType);
92120
}
93121
};

packages/game-bridge/package.json

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"parcel": "^2.13.3"
1515
},
1616
"scripts": {
17-
"build": "parcel build --no-cache --no-scope-hoist",
18-
"build:local": "parcel build --no-cache --no-scope-hoist && pnpm updateSdkVersion",
17+
"build": "parcel build --no-cache --no-scope-hoist && node scripts/fixUnityBuild.js",
18+
"build:local": "parcel build --no-cache --no-scope-hoist && node scripts/fixUnityBuild.js && pnpm updateSdkVersion",
1919
"lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0",
2020
"start": "parcel",
2121
"updateSdkVersion": "./scripts/updateSdkVersion.sh"
@@ -25,17 +25,25 @@
2525
"unity": {
2626
"context": "browser",
2727
"source": "src/index.html",
28+
"outputFormat": "global",
29+
"scopeHoist": false,
30+
"isLibrary": false,
2831
"engines": {
29-
"browsers": "Chrome 90"
32+
"browsers": "Chrome 137"
3033
}
3134
},
3235
"unreal": {
3336
"outputFormat": "global",
3437
"context": "browser",
3538
"source": "src/index.ts",
39+
"scopeHoist": false,
40+
"isLibrary": false,
3641
"engines": {
37-
"browsers": "Chrome 90"
42+
"browsers": "Chrome 137"
3843
}
3944
}
40-
}
45+
},
46+
"browserslist": [
47+
"Chrome 137"
48+
]
4149
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
const DIST_DIR = path.join(__dirname, '..', 'dist', 'unity');
7+
const HTML_FILE = path.join(DIST_DIR, 'index.html');
8+
9+
console.log('🔧 Fixing Unity build...');
10+
console.log(`📁 Dist directory: ${DIST_DIR}`);
11+
12+
if (!fs.existsSync(DIST_DIR)) {
13+
console.error('❌ Dist directory not found!');
14+
process.exit(1);
15+
}
16+
17+
// Find all JS files (excluding .map files)
18+
const jsFiles = fs.readdirSync(DIST_DIR)
19+
.filter(f => f.endsWith('.js') && !f.endsWith('.map'))
20+
.sort((a, b) => {
21+
// Main bundle first
22+
if (a.startsWith('game-bridge')) return -1;
23+
if (b.startsWith('game-bridge')) return 1;
24+
return a.localeCompare(b);
25+
});
26+
27+
console.log(`📦 Found ${jsFiles.length} JS file(s):`, jsFiles);
28+
29+
if (jsFiles.length === 0) {
30+
console.error('❌ No JS files found to inline!');
31+
process.exit(1);
32+
}
33+
34+
// Combine all JS files
35+
let combinedJs = '';
36+
for (const jsFile of jsFiles) {
37+
const jsPath = path.join(DIST_DIR, jsFile);
38+
const jsContent = fs.readFileSync(jsPath, 'utf8');
39+
combinedJs += jsContent + '\n';
40+
console.log(` ✅ ${jsFile}: ${jsContent.length} bytes`);
41+
}
42+
43+
console.log(`📊 Total combined JavaScript: ${combinedJs.length} bytes`);
44+
45+
// Create new HTML with inlined JavaScript
46+
const html = `<!DOCTYPE html>
47+
<html lang="en">
48+
<head>
49+
<meta charset="utf-8">
50+
<title>GameSDK Bridge</title>
51+
<script>${combinedJs}</script>
52+
</head>
53+
<body>
54+
</body>
55+
</html>`;
56+
57+
// Write the new HTML file
58+
fs.writeFileSync(HTML_FILE, html, 'utf8');
59+
console.log(`✅ Unity build fixed successfully!`);
60+
console.log(`📄 Output: ${HTML_FILE} (${html.length} bytes)`);
61+
62+
// Clean up: remove JS files and source maps
63+
console.log('🧹 Cleaning up external JS files...');
64+
for (const jsFile of jsFiles) {
65+
const jsPath = path.join(DIST_DIR, jsFile);
66+
fs.unlinkSync(jsPath);
67+
console.log(` 🗑️ Removed ${jsFile}`);
68+
69+
const mapPath = jsPath + '.map';
70+
if (fs.existsSync(mapPath)) {
71+
fs.unlinkSync(mapPath);
72+
console.log(` 🗑️ Removed ${jsFile}.map`);
73+
}
74+
}
75+
76+
console.log('✨ Done!');
77+

packages/game-bridge/src/index.html

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44
<head>
55
<meta charset="utf-8">
66
<title>GameSDK Bridge</title>
7-
<script type="module">
8-
import "./index.ts";
9-
</script>
7+
<script type="module" src="./index.ts"></script>
108
</head>
119

1210
<body>
13-
<h1>Bridge Running</h1>
1411
</body>
1512

1613
</html>

packages/game-bridge/src/index.ts

Lines changed: 124 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,57 @@ const PASSPORT_FUNCTIONS = {
7272
},
7373
};
7474

75+
function getHttpErrorSummary(err: unknown): {
76+
status?: number;
77+
fullUrl?: string;
78+
traceId?: string;
79+
requestId?: string;
80+
cfRay?: string;
81+
} {
82+
const e: any = err as any;
83+
const status: number | undefined = e?.response?.status;
84+
const url: string | undefined = e?.config?.url;
85+
const baseURL: string | undefined = e?.config?.baseURL;
86+
let fullUrl = typeof url === 'string' && typeof baseURL === 'string' && !/^https?:\/\//i.test(url)
87+
? `${baseURL}${url}`
88+
: url;
89+
90+
// Remove query parameters to avoid exposing sensitive data (tokens, API keys, etc.)
91+
if (typeof fullUrl === 'string') {
92+
const queryIndex = fullUrl.indexOf('?');
93+
if (queryIndex !== -1) {
94+
fullUrl = fullUrl.substring(0, queryIndex);
95+
}
96+
}
97+
98+
const headers: Record<string, string> | undefined = e?.response?.headers;
99+
const traceId = headers?.['x-amzn-trace-id'] ?? headers?.['x-trace-id'];
100+
const requestId = headers?.['x-amzn-requestid']
101+
?? headers?.['x-amzn-request-id']
102+
?? headers?.['x-request-id'];
103+
const cfRay = headers?.['cf-ray'];
104+
105+
return {
106+
status,
107+
fullUrl,
108+
traceId,
109+
requestId,
110+
cfRay,
111+
};
112+
}
113+
114+
function parseHttpStatusSuffix(message: string): { status?: number; url?: string } {
115+
// Matches our existing suffix formats like:
116+
// "... [httpStatus=500 url=https://...]" or
117+
// "... [httpStatus=500 url=https://... trace=... ...]"
118+
const m = message.match(/\[httpStatus=([^\s\]]+)\s+url=([^\s\]]+)[^\]]*\]/);
119+
if (!m) return {};
120+
const statusStr = m[1];
121+
const url = m[2];
122+
const status = statusStr && /^[0-9]+$/.test(statusStr) ? Number(statusStr) : undefined;
123+
return { status, url };
124+
}
125+
75126
// To notify game engine that this file is loaded
76127
const initRequest = 'init';
77128
const initRequestId = '1';
@@ -126,27 +177,31 @@ type VersionInfo = {
126177

127178
const callbackToGame = (data: CallbackData) => {
128179
const message = JSON.stringify(data);
129-
console.log(`callbackToGame: ${message}`);
130-
if (typeof window.ue !== 'undefined') {
131-
if (typeof window.ue.jsconnector === 'undefined') {
132-
const unrealError = 'Unreal JSConnector not defined';
133-
console.error(unrealError);
134-
throw new Error(unrealError);
135-
} else {
180+
181+
try {
182+
if (typeof window.ue !== 'undefined') {
183+
if (typeof window.ue.jsconnector === 'undefined') {
184+
const unrealError = 'Unreal JSConnector not defined';
185+
console.error('[GAME-BRIDGE]', unrealError);
186+
throw new Error(unrealError);
187+
}
136188
window.ue.jsconnector.sendtogame(message);
189+
} else if (typeof blu_event !== 'undefined') {
190+
blu_event('sendtogame', message);
191+
} else if (typeof UnityPostMessage !== 'undefined') {
192+
UnityPostMessage(message);
193+
} else if (typeof window.Unity !== 'undefined') {
194+
window.Unity.call(message);
195+
} else if (typeof window.uwb !== 'undefined') {
196+
window.uwb.ExecuteJsMethod('callback', message);
197+
} else {
198+
const gameBridgeError = 'No available game callbacks to call from ImmutableSDK game-bridge';
199+
console.error('[GAME-BRIDGE]', gameBridgeError);
200+
throw new Error(gameBridgeError);
137201
}
138-
} else if (typeof blu_event !== 'undefined') {
139-
blu_event('sendtogame', message);
140-
} else if (typeof UnityPostMessage !== 'undefined') {
141-
UnityPostMessage(message);
142-
} else if (typeof window.Unity !== 'undefined') {
143-
window.Unity.call(message);
144-
} else if (typeof window.uwb !== 'undefined') {
145-
window.uwb.ExecuteJsMethod('callback', message);
146-
} else {
147-
const gameBridgeError = 'No available game callbacks to call from ImmutableSDK game-bridge';
148-
console.error(gameBridgeError);
149-
throw new Error(gameBridgeError);
202+
} catch (error) {
203+
console.error('[GAME-BRIDGE] Error in callbackToGame:', error);
204+
throw error;
150205
}
151206
};
152207

@@ -224,9 +279,9 @@ window.callFunction = async (jsonData: string) => {
224279
const request = JSON.parse(data);
225280
const redirect: string | null = request?.redirectUri;
226281
const logoutMode: 'silent' | 'redirect' = request?.isSilentLogout === true ? 'silent' : 'redirect';
282+
227283
if (!passportClient || passportInitData !== data) {
228284
passportInitData = data;
229-
console.log(`Connecting to ${request.environment} environment`);
230285

231286
let passportConfig: passport.PassportModuleConfiguration;
232287

@@ -259,6 +314,8 @@ window.callFunction = async (jsonData: string) => {
259314
chainID: 5,
260315
coreContractAddress: '0xd05323731807A35599BF9798a1DE15e89d6D6eF1',
261316
registrationContractAddress: '0x7EB840223a3b1E0e8D54bF8A6cd83df5AFfC88B2',
317+
sdkVersion: sdkVersionTag,
318+
baseConfig,
262319
}),
263320
},
264321
}),
@@ -283,9 +340,15 @@ window.callFunction = async (jsonData: string) => {
283340
};
284341
}
285342

286-
passportClient = new passport.Passport(passportConfig);
287-
trackDuration(moduleName, 'initialisedPassport', mt(markStart));
343+
try {
344+
passportClient = new passport.Passport(passportConfig);
345+
trackDuration(moduleName, 'initialisedPassport', mt(markStart));
346+
} catch (initError) {
347+
console.error('[GAME-BRIDGE] Error creating Passport client:', initError);
348+
throw initError;
349+
}
288350
}
351+
289352
callbackToGame({
290353
responseFor: fxName,
291354
requestId,
@@ -805,6 +868,41 @@ window.callFunction = async (jsonData: string) => {
805868
wrappedError = error;
806869
}
807870

871+
// Make endpoint visible in Unity Output for debugging (CI logs don't include JS console).
872+
const {
873+
status,
874+
fullUrl,
875+
traceId,
876+
requestId: httpRequestId,
877+
cfRay,
878+
} = getHttpErrorSummary(error);
879+
if (
880+
fxName === PASSPORT_FUNCTIONS.imx.registerOffchain
881+
&& wrappedError instanceof Error
882+
) {
883+
// Some upstream errors embed "[httpStatus=... url=...]" only in the message string
884+
// without preserving axios-like fields. Parse what we can so we can still enrich.
885+
const parsed = parseHttpStatusSuffix(wrappedError.message);
886+
const effectiveStatus = status ?? parsed.status;
887+
const effectiveUrl = fullUrl ?? parsed.url;
888+
const suffix = ` [httpStatus=${effectiveStatus ?? 'unknown'}`
889+
+ ` url=${effectiveUrl ?? 'unknown'}`
890+
+ ` trace=${traceId ?? 'unknown'}`
891+
+ ` reqId=${httpRequestId ?? 'unknown'}`
892+
+ ` cfRay=${cfRay ?? 'unknown'}]`;
893+
// If a previous layer already added a minimal suffix like:
894+
// "... [httpStatus=500 url=...]"
895+
// upgrade it to include trace/reqId/resp instead of skipping.
896+
if (wrappedError.message.includes('[httpStatus=')) {
897+
if (!wrappedError.message.includes('trace=')) {
898+
const upgraded = wrappedError.message.replace(/\[httpStatus=[^\]]*\]/g, suffix.trim());
899+
wrappedError = new Error(upgraded);
900+
}
901+
} else {
902+
wrappedError = new Error(`${wrappedError.message}${suffix}`);
903+
}
904+
}
905+
808906
const errorType = error instanceof passport.PassportError
809907
? error?.type
810908
: undefined;
@@ -827,7 +925,10 @@ window.callFunction = async (jsonData: string) => {
827925
responseFor: fxName,
828926
requestId,
829927
success: false,
830-
error: error?.message !== null && error?.message !== undefined ? error.message : 'Error',
928+
// IMPORTANT: return the wrapped error message so we include extra HTTP diagnostics
929+
// (httpStatus/url/trace/reqId/resp) in Unity CI logs.
930+
error: wrappedError?.message
931+
?? (error?.message !== null && error?.message !== undefined ? error.message : 'Error'),
831932
errorType: error instanceof passport.PassportError ? error?.type : null,
832933
});
833934
}

0 commit comments

Comments
 (0)