@skillprint/cocos-sdk 0.0.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/cc.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ declare module 'cc' {
2
+ export const _decorator: {
3
+ ccclass(name?: string): Function;
4
+ property(options?: any): Function;
5
+ };
6
+ export class Component {
7
+ node: any;
8
+ enabled: boolean;
9
+ start(): void;
10
+ onLoad(): void;
11
+ onDestroy(): void;
12
+ schedule(callback: any, interval?: number, repeat?: number, delay?: number): void;
13
+ unschedule(callback: any): void;
14
+ getComponent<T>(type: any): T | null;
15
+ addComponent<T>(type: any): T;
16
+ }
17
+ export class Camera {
18
+ targetTexture: any;
19
+ node: any;
20
+ enabled: boolean;
21
+ render(): void;
22
+ }
23
+ export class RenderTexture {
24
+ reset(config: any): void;
25
+ readPixels(x?: number, y?: number, width?: number, height?: number, buffer?: Uint8Array): Uint8Array;
26
+ destroy(): void;
27
+ }
28
+ export const view: {
29
+ getVisibleSize(): { width: number; height: number };
30
+ };
31
+ export const sys: {
32
+ isBrowser: boolean;
33
+ isNative: boolean;
34
+ localStorage: {
35
+ setItem(key: string, value: string): void;
36
+ getItem(key: string): string | null;
37
+ };
38
+ };
39
+ export const native: {
40
+ fileUtils: {
41
+ getWritablePath(): string;
42
+ getDataFromFile(filePath: string): ArrayBuffer | null;
43
+ removeFile(filePath: string): boolean;
44
+ };
45
+ saveImageData(data: Uint8Array, width: number, height: number, filePath: string): Promise<void>;
46
+ };
47
+ export const director: {
48
+ addPersistRootNode(node: any): void;
49
+ getScene(): {
50
+ children: any[];
51
+ } | null;
52
+ once(event: string, callback: Function, target?: any): void;
53
+ };
54
+ export const Director: {
55
+ EVENT_AFTER_RENDER: string;
56
+ };
57
+ export function Enum(obj: any): any;
58
+ }
@@ -0,0 +1,27 @@
1
+ import { Camera } from 'cc';
2
+ export declare class ScreenshotUtility {
3
+ private logger;
4
+ constructor(logger?: (msg: string, level: 'Info' | 'Warning' | 'Error') => void);
5
+ private log;
6
+ /**
7
+ * Captures a screenshot from the specified camera, flips it vertically,
8
+ * downscales it if necessary, and returns a JPEG Blob (Web) or ArrayBuffer (Native).
9
+ */
10
+ captureScreenshot(camera: Camera, maxWidth?: number, jpegQuality?: number): Promise<Blob | ArrayBuffer | null>;
11
+ /**
12
+ * Vertically flips the raw pixel buffer in-place.
13
+ */
14
+ private flipY;
15
+ /**
16
+ * HTML5 Canvas based JPEG encoder and scaling for web platforms
17
+ */
18
+ private encodeOnWeb;
19
+ /**
20
+ * Native file and image API based JPEG encoder for Android/iOS/Desktop
21
+ */
22
+ private encodeOnNative;
23
+ /**
24
+ * Simple pixel downsampling helper for native builds without canvas API
25
+ */
26
+ private downsamplePixels;
27
+ }
@@ -0,0 +1,216 @@
1
+ import { RenderTexture, view, sys, native, director, Director } from 'cc';
2
+ export class ScreenshotUtility {
3
+ constructor(logger) {
4
+ this.logger = null;
5
+ if (logger) {
6
+ this.logger = logger;
7
+ }
8
+ }
9
+ log(msg, level = 'Info') {
10
+ if (this.logger) {
11
+ this.logger(msg, level);
12
+ }
13
+ else {
14
+ const prefix = `[ScreenshotUtility] [${level}]`;
15
+ if (level === 'Error') {
16
+ console.error(`${prefix} ${msg}`);
17
+ }
18
+ else if (level === 'Warning') {
19
+ console.warn(`${prefix} ${msg}`);
20
+ }
21
+ else {
22
+ console.log(`${prefix} ${msg}`);
23
+ }
24
+ }
25
+ }
26
+ /**
27
+ * Captures a screenshot from the specified camera, flips it vertically,
28
+ * downscales it if necessary, and returns a JPEG Blob (Web) or ArrayBuffer (Native).
29
+ */
30
+ async captureScreenshot(camera, maxWidth = 960, jpegQuality = 60) {
31
+ if (!camera) {
32
+ this.log('Cannot capture: Camera is null', 'Error');
33
+ return null;
34
+ }
35
+ try {
36
+ const visibleSize = view.getVisibleSize();
37
+ const width = Math.floor(visibleSize.width);
38
+ const height = Math.floor(visibleSize.height);
39
+ this.log(`Capturing screenshot: resolution ${width}x${height}`, 'Info');
40
+ // 1. Create RenderTexture
41
+ const renderTexture = new RenderTexture();
42
+ renderTexture.reset({
43
+ width: width,
44
+ height: height
45
+ });
46
+ // 2. Render camera into the texture by waiting for the render pipeline cycle
47
+ const prevTarget = camera.targetTexture;
48
+ camera.targetTexture = renderTexture;
49
+ await new Promise((resolve) => {
50
+ director.once(Director.EVENT_AFTER_RENDER, resolve);
51
+ });
52
+ camera.targetTexture = prevTarget; // restore camera settings
53
+ // 3. Read pixels from GPU
54
+ const pixelBuffer = new Uint8Array(width * height * 4);
55
+ renderTexture.readPixels(0, 0, width, height, pixelBuffer);
56
+ // Clean up RenderTexture immediately to free GPU memory
57
+ renderTexture.destroy();
58
+ // 4. Flip pixel coordinates vertically (WebGL reads bottom-to-top)
59
+ this.flipY(pixelBuffer, width, height);
60
+ // 5. Handle Platform-Specific Encoding
61
+ if (sys.isBrowser) {
62
+ return await this.encodeOnWeb(pixelBuffer, width, height, maxWidth, jpegQuality);
63
+ }
64
+ else if (sys.isNative && !!native && typeof native.saveImageData === 'function') {
65
+ return await this.encodeOnNative(pixelBuffer, width, height, maxWidth, jpegQuality);
66
+ }
67
+ else {
68
+ this.log('Platform not supported for screenshot encoding. Returning raw buffer.', 'Warning');
69
+ return pixelBuffer.buffer;
70
+ }
71
+ }
72
+ catch (err) {
73
+ this.log(`Failed to capture screenshot: ${err.message || err}`, 'Error');
74
+ return null;
75
+ }
76
+ }
77
+ /**
78
+ * Vertically flips the raw pixel buffer in-place.
79
+ */
80
+ flipY(pixels, width, height) {
81
+ const rowBytes = width * 4;
82
+ const tempRow = new Uint8Array(rowBytes);
83
+ for (let row = 0; row < Math.floor(height / 2); row++) {
84
+ const topOffset = row * rowBytes;
85
+ const bottomOffset = (height - 1 - row) * rowBytes;
86
+ // Swap top and bottom rows
87
+ tempRow.set(pixels.subarray(topOffset, topOffset + rowBytes));
88
+ pixels.set(pixels.subarray(bottomOffset, bottomOffset + rowBytes), topOffset);
89
+ pixels.set(tempRow, bottomOffset);
90
+ }
91
+ }
92
+ /**
93
+ * HTML5 Canvas based JPEG encoder and scaling for web platforms
94
+ */
95
+ encodeOnWeb(pixels, width, height, maxWidth, jpegQuality) {
96
+ return new Promise((resolve) => {
97
+ try {
98
+ // Determine target dimensions
99
+ let destWidth = width;
100
+ let destHeight = height;
101
+ if (maxWidth > 0 && width > maxWidth) {
102
+ const scale = maxWidth / width;
103
+ destWidth = maxWidth;
104
+ destHeight = Math.floor(height * scale);
105
+ }
106
+ // Create offscreen canvas for rendering
107
+ const canvas = document.createElement('canvas');
108
+ canvas.width = destWidth;
109
+ canvas.height = destHeight;
110
+ const ctx = canvas.getContext('2d');
111
+ if (!ctx) {
112
+ this.log('Could not get HTML5 canvas 2D context', 'Error');
113
+ resolve(null);
114
+ return;
115
+ }
116
+ if (destWidth === width && destHeight === height) {
117
+ // Direct copy
118
+ const imgData = ctx.createImageData(width, height);
119
+ imgData.data.set(pixels);
120
+ ctx.putImageData(imgData, 0, 0);
121
+ }
122
+ else {
123
+ // Downscale using intermediate canvas drawing
124
+ const srcCanvas = document.createElement('canvas');
125
+ srcCanvas.width = width;
126
+ srcCanvas.height = height;
127
+ const srcCtx = srcCanvas.getContext('2d');
128
+ if (srcCtx) {
129
+ const imgData = srcCtx.createImageData(width, height);
130
+ imgData.data.set(pixels);
131
+ srcCtx.putImageData(imgData, 0, 0);
132
+ // Render downscaled image
133
+ ctx.drawImage(srcCanvas, 0, 0, destWidth, destHeight);
134
+ }
135
+ else {
136
+ resolve(null);
137
+ return;
138
+ }
139
+ }
140
+ const qualityFactor = Math.max(0.01, Math.min(1.0, jpegQuality / 100));
141
+ canvas.toBlob((blob) => {
142
+ resolve(blob);
143
+ }, 'image/jpeg', qualityFactor);
144
+ }
145
+ catch (err) {
146
+ this.log(`Web encoding failed: ${err.message || err}`, 'Error');
147
+ resolve(null);
148
+ }
149
+ });
150
+ }
151
+ /**
152
+ * Native file and image API based JPEG encoder for Android/iOS/Desktop
153
+ */
154
+ async encodeOnNative(pixels, width, height, maxWidth, jpegQuality) {
155
+ try {
156
+ // Note: Cocos Creator Native saveImageData saves raw pixel buffers to file.
157
+ // Under the hood, if the filename has a .jpg extension, it handles JPEG compression.
158
+ const writablePath = native.fileUtils.getWritablePath();
159
+ const filePath = `${writablePath}temp_screenshot.jpg`;
160
+ // If downscaling is needed natively, we pass smaller dimensions if native API supports scaling,
161
+ // otherwise saveImageData handles saving raw buffer.
162
+ let targetWidth = width;
163
+ let targetHeight = height;
164
+ let finalPixels = pixels;
165
+ if (maxWidth > 0 && width > maxWidth) {
166
+ const scale = maxWidth / width;
167
+ targetWidth = maxWidth;
168
+ targetHeight = Math.floor(height * scale);
169
+ // Perform a simple downsampling if size is reduced (e.g. skip pixels)
170
+ // In Cocos 3.x, saveImageData is standard, so we can save it and let the engine compress it.
171
+ // We'll downsample finalPixels array to match target size
172
+ finalPixels = this.downsamplePixels(pixels, width, height, targetWidth, targetHeight);
173
+ }
174
+ // Save image using Cocos native APIs (saves as JPEG on .jpg extension)
175
+ // Note: native.saveImageData returns a Promise or runs synchronously. In 3.8.x, it returns a Promise.
176
+ await native.saveImageData(finalPixels, targetWidth, targetHeight, filePath);
177
+ // Read the saved file back into memory
178
+ const arrayBuffer = native.fileUtils.getDataFromFile(filePath);
179
+ // Clean up the temporary file
180
+ native.fileUtils.removeFile(filePath);
181
+ if (arrayBuffer && arrayBuffer.byteLength > 0) {
182
+ this.log(`Native screenshot encoded successfully. Size: ${arrayBuffer.byteLength} bytes`, 'Info');
183
+ return arrayBuffer;
184
+ }
185
+ else {
186
+ this.log('Failed to read native saved screenshot back from local storage', 'Error');
187
+ return null;
188
+ }
189
+ }
190
+ catch (err) {
191
+ this.log(`Native encoding failed: ${err.message || err}`, 'Error');
192
+ return null;
193
+ }
194
+ }
195
+ /**
196
+ * Simple pixel downsampling helper for native builds without canvas API
197
+ */
198
+ downsamplePixels(pixels, srcW, srcH, destW, destH) {
199
+ const destPixels = new Uint8Array(destW * destH * 4);
200
+ const xRatio = srcW / destW;
201
+ const yRatio = srcH / destH;
202
+ for (let y = 0; y < destH; y++) {
203
+ for (let x = 0; x < destW; x++) {
204
+ const srcX = Math.floor(x * xRatio);
205
+ const srcY = Math.floor(y * yRatio);
206
+ const srcIdx = (srcY * srcW + srcX) * 4;
207
+ const destIdx = (y * destW + x) * 4;
208
+ destPixels[destIdx] = pixels[srcIdx]; // R
209
+ destPixels[destIdx + 1] = pixels[srcIdx + 1]; // G
210
+ destPixels[destIdx + 2] = pixels[srcIdx + 2]; // B
211
+ destPixels[destIdx + 3] = pixels[srcIdx + 3]; // A
212
+ }
213
+ }
214
+ return destPixels;
215
+ }
216
+ }
@@ -0,0 +1,23 @@
1
+ import { ParameterInfo, ParameterUpdateResult } from './SkillprintTypes';
2
+ export declare class SkillprintAPIClient {
3
+ private baseUrl;
4
+ private partnerApiKey;
5
+ private userToken;
6
+ private logger;
7
+ private static readonly START_SESSION_ENDPOINT;
8
+ private static readonly UPLOAD_SCREENSHOTS_ENDPOINT;
9
+ private static readonly POLL_RESULTS_ENDPOINT;
10
+ private static readonly CREATE_USER_ENDPOINT;
11
+ private static readonly GET_USER_TOKEN_ENDPOINT;
12
+ private static readonly MAX_UPLOAD_RETRIES;
13
+ private static readonly RETRY_BASE_DELAY_MS;
14
+ constructor(baseUrl: string, partnerApiKey: string, logger?: (msg: string, level: 'Info' | 'Warning' | 'Error') => void);
15
+ private log;
16
+ startSession(sessionId: string, targetMood: string, customPlayerId: string | null, gameName: string, gameParameters: ParameterInfo[]): Promise<boolean>;
17
+ postScreenshots(sessionId: string, screenshots: (Blob | ArrayBuffer)[], isLastChunk: boolean): Promise<boolean>;
18
+ pollParameterResults(sessionId: string): Promise<ParameterUpdateResult[]>;
19
+ getParsedValue(update: ParameterUpdateResult): any;
20
+ createOrGetUserToken(customPlayerId: string): Promise<string | null>;
21
+ private createUser;
22
+ private getUserToken;
23
+ }
@@ -0,0 +1,284 @@
1
+ export class SkillprintAPIClient {
2
+ constructor(baseUrl, partnerApiKey, logger) {
3
+ this.userToken = null;
4
+ this.logger = null;
5
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
6
+ this.partnerApiKey = partnerApiKey;
7
+ if (logger) {
8
+ this.logger = logger;
9
+ }
10
+ }
11
+ log(msg, level = 'Info') {
12
+ if (this.logger) {
13
+ this.logger(msg, level);
14
+ }
15
+ else {
16
+ const prefix = `[SkillprintAPIClient] [${level}]`;
17
+ if (level === 'Error') {
18
+ console.error(`${prefix} ${msg}`);
19
+ }
20
+ else if (level === 'Warning') {
21
+ console.warn(`${prefix} ${msg}`);
22
+ }
23
+ else {
24
+ console.log(`${prefix} ${msg}`);
25
+ }
26
+ }
27
+ }
28
+ async startSession(sessionId, targetMood, customPlayerId, gameName, gameParameters) {
29
+ const url = `${this.baseUrl}${SkillprintAPIClient.START_SESSION_ENDPOINT}`;
30
+ this.log(`Starting session: POST ${url}`, 'Info');
31
+ this.log(`Starting session: MOOD ${targetMood}`, 'Warning');
32
+ // Provision/retrieve user token if customPlayerId is provided
33
+ if (customPlayerId) {
34
+ try {
35
+ this.userToken = await this.createOrGetUserToken(customPlayerId);
36
+ if (this.userToken) {
37
+ this.log(`User token obtained successfully for player: ${customPlayerId}`, 'Info');
38
+ }
39
+ else {
40
+ this.log(`Could not retrieve user token for player: ${customPlayerId}. Continuing without token.`, 'Warning');
41
+ }
42
+ }
43
+ catch (err) {
44
+ this.log(`Error obtaining user token: ${err.message || err}. Continuing without token.`, 'Warning');
45
+ }
46
+ }
47
+ const requestData = {
48
+ sessionId: sessionId,
49
+ game: gameName,
50
+ targetMood: targetMood,
51
+ gameParameters: gameParameters
52
+ };
53
+ const headers = {
54
+ 'Content-Type': 'application/json',
55
+ 'Authorization': `Api-Key ${this.partnerApiKey}`
56
+ };
57
+ if (this.userToken) {
58
+ headers['X-Auth-Token'] = `Token ${this.userToken}`;
59
+ }
60
+ try {
61
+ const response = await fetch(url, {
62
+ method: 'POST',
63
+ headers: headers,
64
+ body: JSON.stringify(requestData)
65
+ });
66
+ const text = await response.text();
67
+ if (response.ok) {
68
+ this.log(`StartSession successful. Response: ${text}`, 'Info');
69
+ return true;
70
+ }
71
+ else {
72
+ this.log(`StartSession Error: ${response.statusText}. Response: ${text}`, 'Error');
73
+ return false;
74
+ }
75
+ }
76
+ catch (err) {
77
+ this.log(`StartSession Network Error: ${err.message || err}`, 'Error');
78
+ return false;
79
+ }
80
+ }
81
+ async postScreenshots(sessionId, screenshots, isLastChunk) {
82
+ const endpoint = SkillprintAPIClient.UPLOAD_SCREENSHOTS_ENDPOINT.replace('{sessionId}', sessionId);
83
+ const url = `${this.baseUrl}${endpoint}`;
84
+ this.log(`Posting ${screenshots.length} screenshots (isLastChunk: ${isLastChunk}): POST ${url}`, 'Info');
85
+ if (screenshots.length === 0 && !isLastChunk) {
86
+ this.log(`No screenshots provided, and isLastChunk is false. Skipping.`, 'Warning');
87
+ return false;
88
+ }
89
+ let attempt = 0;
90
+ let succeeded = false;
91
+ let lastError = '';
92
+ while (attempt <= SkillprintAPIClient.MAX_UPLOAD_RETRIES && !succeeded) {
93
+ if (attempt > 0) {
94
+ const delay = SkillprintAPIClient.RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
95
+ this.log(`Retrying screenshot upload (attempt ${attempt + 1}/${SkillprintAPIClient.MAX_UPLOAD_RETRIES + 1}) after ${delay}ms...`, 'Warning');
96
+ await new Promise((resolve) => setTimeout(resolve, delay));
97
+ }
98
+ try {
99
+ const formData = new FormData();
100
+ formData.append('is_last_chunk', isLastChunk.toString().toLowerCase());
101
+ screenshots.forEach((screenshot, i) => {
102
+ const blob = screenshot instanceof ArrayBuffer ? new Blob([screenshot], { type: 'image/jpeg' }) : screenshot;
103
+ formData.append(`screenshot${i}`, blob, `screenshot_${i}.jpg`);
104
+ });
105
+ const headers = {
106
+ 'Authorization': `Api-Key ${this.partnerApiKey}`
107
+ };
108
+ if (this.userToken) {
109
+ headers['X-Auth-Token'] = `Token ${this.userToken}`;
110
+ }
111
+ // Note: Do not set Content-Type header when sending FormData.
112
+ // The browser will automatically set the correct multipart/form-data boundary.
113
+ const response = await fetch(url, {
114
+ method: 'POST',
115
+ headers: headers,
116
+ body: formData
117
+ });
118
+ const text = await response.text();
119
+ if (response.ok) {
120
+ this.log(`PostScreenshots successful. Response: ${text}`, 'Info');
121
+ succeeded = true;
122
+ }
123
+ else {
124
+ lastError = `Status: ${response.statusText} | Response: ${text}`;
125
+ this.log(`PostScreenshots Error (attempt ${attempt + 1}): ${lastError}`, 'Error');
126
+ }
127
+ }
128
+ catch (err) {
129
+ lastError = err.message || String(err);
130
+ this.log(`PostScreenshots Network Error (attempt ${attempt + 1}): ${lastError}`, 'Error');
131
+ }
132
+ attempt++;
133
+ }
134
+ if (!succeeded) {
135
+ this.log(`PostScreenshots failed after ${SkillprintAPIClient.MAX_UPLOAD_RETRIES + 1} attempts. Last error: ${lastError}`, 'Error');
136
+ }
137
+ return succeeded;
138
+ }
139
+ async pollParameterResults(sessionId) {
140
+ const endpoint = SkillprintAPIClient.POLL_RESULTS_ENDPOINT.replace('{sessionId}', sessionId);
141
+ const url = `${this.baseUrl}${endpoint}`;
142
+ this.log(`Polling results: GET ${url}`, 'Info');
143
+ const headers = {
144
+ 'Authorization': `Api-Key ${this.partnerApiKey}`,
145
+ 'Accept': 'application/json'
146
+ };
147
+ if (this.userToken) {
148
+ headers['X-Auth-Token'] = `Token ${this.userToken}`;
149
+ }
150
+ try {
151
+ const response = await fetch(url, {
152
+ method: 'GET',
153
+ headers: headers
154
+ });
155
+ const text = await response.text();
156
+ if (response.ok) {
157
+ this.log(`PollResults successful. Response: ${text}`, 'Info');
158
+ let data;
159
+ try {
160
+ data = JSON.parse(text);
161
+ }
162
+ catch (jsonErr) {
163
+ // Try parsing as array directly if it was wrapped or malformed
164
+ if (text.trim().startsWith('[')) {
165
+ data = { parameterUpdates: JSON.parse(text) };
166
+ }
167
+ else {
168
+ throw jsonErr;
169
+ }
170
+ }
171
+ let updates = [];
172
+ if (data && Array.isArray(data.parameterUpdates)) {
173
+ updates = data.parameterUpdates;
174
+ }
175
+ else if (Array.isArray(data)) {
176
+ updates = data;
177
+ }
178
+ // Map updates to extract the parsed values
179
+ updates.forEach((update) => {
180
+ this.log(`Parameter Update: ${update.parameterName} = ${this.getParsedValue(update)}`, 'Info');
181
+ });
182
+ return updates;
183
+ }
184
+ else {
185
+ this.log(`PollResults Error: ${response.statusText}. Response: ${text}`, 'Error');
186
+ return [];
187
+ }
188
+ }
189
+ catch (err) {
190
+ this.log(`PollResults Error: ${err.message || err}`, 'Error');
191
+ return [];
192
+ }
193
+ }
194
+ getParsedValue(update) {
195
+ // Retrieve the newValue or find the key with the parameterName (for legacy or alternative responses)
196
+ if (update.newValue !== undefined && update.newValue !== null) {
197
+ return update.newValue;
198
+ }
199
+ if (update.parameterName && update[update.parameterName] !== undefined) {
200
+ return update[update.parameterName];
201
+ }
202
+ return null;
203
+ }
204
+ async createOrGetUserToken(customPlayerId) {
205
+ if (!customPlayerId)
206
+ return null;
207
+ // Try getting token first
208
+ let token = await this.getUserToken(customPlayerId);
209
+ if (token)
210
+ return token;
211
+ // User doesn't exist, create user
212
+ const created = await this.createUser(customPlayerId);
213
+ if (created) {
214
+ // Retrieve token again after creation
215
+ token = await this.getUserToken(customPlayerId);
216
+ return token;
217
+ }
218
+ return null;
219
+ }
220
+ async createUser(internalId) {
221
+ const url = `${this.baseUrl}${SkillprintAPIClient.CREATE_USER_ENDPOINT}`;
222
+ this.log(`Creating user: POST ${url} with internalId: ${internalId}`, 'Info');
223
+ try {
224
+ const response = await fetch(url, {
225
+ method: 'POST',
226
+ headers: {
227
+ 'Content-Type': 'application/json',
228
+ 'Authorization': `Api-Key ${this.partnerApiKey}`
229
+ },
230
+ body: JSON.stringify({ internalId: internalId })
231
+ });
232
+ const text = await response.text();
233
+ if (response.ok) {
234
+ this.log(`CreateUser successful. Response: ${text}`, 'Info');
235
+ return true;
236
+ }
237
+ else {
238
+ this.log(`CreateUser Error: ${response.statusText}. Response: ${text}`, 'Error');
239
+ return false;
240
+ }
241
+ }
242
+ catch (err) {
243
+ this.log(`CreateUser Network Error: ${err.message || err}`, 'Error');
244
+ return false;
245
+ }
246
+ }
247
+ async getUserToken(internalId) {
248
+ const url = `${this.baseUrl}${SkillprintAPIClient.GET_USER_TOKEN_ENDPOINT}`;
249
+ this.log(`Getting user token: POST ${url} with internalId: ${internalId}`, 'Info');
250
+ try {
251
+ const response = await fetch(url, {
252
+ method: 'POST',
253
+ headers: {
254
+ 'Content-Type': 'application/json',
255
+ 'Authorization': `Api-Key ${this.partnerApiKey}`
256
+ },
257
+ body: JSON.stringify({ internalId: internalId })
258
+ });
259
+ const text = await response.text();
260
+ if (response.ok) {
261
+ this.log(`GetUserToken successful. Response: ${text}`, 'Info');
262
+ const data = JSON.parse(text);
263
+ return data.token || null;
264
+ }
265
+ else {
266
+ this.log(`GetUserToken Error: ${response.statusText}. Response: ${text}`, 'Error');
267
+ return null;
268
+ }
269
+ }
270
+ catch (err) {
271
+ this.log(`GetUserToken Network Error: ${err.message || err}`, 'Error');
272
+ return null;
273
+ }
274
+ }
275
+ }
276
+ // Endpoints
277
+ SkillprintAPIClient.START_SESSION_ENDPOINT = '/games/api/sessions/';
278
+ SkillprintAPIClient.UPLOAD_SCREENSHOTS_ENDPOINT = '/games/api/record-session/{sessionId}/';
279
+ SkillprintAPIClient.POLL_RESULTS_ENDPOINT = '/games/api/sessions/{sessionId}/';
280
+ SkillprintAPIClient.CREATE_USER_ENDPOINT = '/partners/api/users/add/';
281
+ SkillprintAPIClient.GET_USER_TOKEN_ENDPOINT = '/partners/api/users/auth/token/';
282
+ // Retry settings
283
+ SkillprintAPIClient.MAX_UPLOAD_RETRIES = 2;
284
+ SkillprintAPIClient.RETRY_BASE_DELAY_MS = 1000;
@@ -0,0 +1,17 @@
1
+ import { Component } from 'cc';
2
+ import { ApiEnvironment, SkillprintConfig } from './SkillprintTypes';
3
+ export declare class SkillprintConfigComponent extends Component {
4
+ gameName: string;
5
+ targetEnvironment: ApiEnvironment;
6
+ stagingPartnerApiKey: string;
7
+ stagingApiBaseUrl: string;
8
+ productionPartnerApiKey: string;
9
+ productionApiBaseUrl: string;
10
+ screenshotMaxWidth: number;
11
+ screenshotJpegQuality: number;
12
+ screenshotIntervalSeconds: number;
13
+ screenshotPostIntervalSeconds: number;
14
+ pollResultsIntervalSeconds: number;
15
+ enableDebugLogging: boolean;
16
+ getConfig(): SkillprintConfig;
17
+ }