@usions/sdk 2.0.1 → 2.1.0
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/README.md +5 -5
- package/package.json +16 -5
- package/src/browser.js +1392 -1139
- package/src/index.js +4 -49
- package/src/modules/back-button.js +26 -0
- package/src/modules/bot.js +52 -0
- package/src/modules/chat.js +35 -0
- package/src/modules/core.js +207 -0
- package/src/modules/file-storage.js +46 -0
- package/src/modules/game-core.js +149 -0
- package/src/modules/game-direct.js +219 -0
- package/src/modules/game-methods.js +334 -0
- package/src/modules/game-proxy.js +149 -0
- package/src/modules/game-socket.js +195 -0
- package/src/modules/index.js +54 -0
- package/src/modules/misc.js +123 -0
- package/src/modules/results.js +34 -0
- package/src/modules/session.js +62 -0
- package/src/modules/storage.js +64 -0
- package/src/modules/ui.js +111 -0
- package/src/modules/user.js +61 -0
- package/src/modules/wallet.js +114 -0
- package/types/index.d.ts +62 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK Game Socket — Socket.IO connection management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add Socket.IO connection methods to game module
|
|
7
|
+
* @param {object} game - The game module object
|
|
8
|
+
* @param {object} Usion - Reference to the main Usion object
|
|
9
|
+
*/
|
|
10
|
+
export function applyGameSocket(game, Usion) {
|
|
11
|
+
/**
|
|
12
|
+
* Initialize socket connection
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
game._initSocket = function(socketUrl, token, resolve, reject) {
|
|
16
|
+
const self = this;
|
|
17
|
+
|
|
18
|
+
// Prevent creating duplicate sockets
|
|
19
|
+
if (self.socket && self.socket.connected) {
|
|
20
|
+
self._connecting = false;
|
|
21
|
+
resolve();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Clean up any existing disconnected socket
|
|
26
|
+
if (self.socket) {
|
|
27
|
+
self.socket.disconnect();
|
|
28
|
+
self.socket = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
self.socket = io(socketUrl, {
|
|
33
|
+
path: '/socket.io',
|
|
34
|
+
transports: ['websocket', 'polling'],
|
|
35
|
+
auth: { token: token },
|
|
36
|
+
autoConnect: true,
|
|
37
|
+
reconnection: true,
|
|
38
|
+
reconnectionAttempts: 50,
|
|
39
|
+
reconnectionDelay: 1000,
|
|
40
|
+
reconnectionDelayMax: 10000
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
self.socket.on('connect', function() {
|
|
44
|
+
self.connected = true;
|
|
45
|
+
self._connecting = false;
|
|
46
|
+
Usion.log('Game socket connected');
|
|
47
|
+
|
|
48
|
+
// Start heartbeat to keep game session alive
|
|
49
|
+
if (self._heartbeatInterval) clearInterval(self._heartbeatInterval);
|
|
50
|
+
self._heartbeatInterval = setInterval(function() {
|
|
51
|
+
if (self.socket && self.connected && self.roomId) {
|
|
52
|
+
self.socket.emit('game:heartbeat', { room_id: self.roomId });
|
|
53
|
+
}
|
|
54
|
+
}, 25000);
|
|
55
|
+
|
|
56
|
+
// Re-join room after reconnect
|
|
57
|
+
if (self.roomId) {
|
|
58
|
+
self._joined = false;
|
|
59
|
+
self._joinPromise = null;
|
|
60
|
+
self.join(self.roomId)
|
|
61
|
+
.then(function() {
|
|
62
|
+
Usion.log('Reconnected - joined room ' + self.roomId);
|
|
63
|
+
self.requestSync(self._lastSequence || 0);
|
|
64
|
+
})
|
|
65
|
+
.catch(function(err) {
|
|
66
|
+
Usion.log('Rejoin failed: ' + (err && err.message ? err.message : String(err)));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
self.socket.on('connect_error', function(err) {
|
|
74
|
+
self._connecting = false;
|
|
75
|
+
Usion.log('Game socket error: ' + err.message);
|
|
76
|
+
if (self._eventHandlers.connectionError) {
|
|
77
|
+
self._eventHandlers.connectionError(err);
|
|
78
|
+
}
|
|
79
|
+
reject(err);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
self.socket.on('disconnect', function(reason) {
|
|
83
|
+
self.connected = false;
|
|
84
|
+
self._joined = false;
|
|
85
|
+
self._joinPromise = null;
|
|
86
|
+
if (self._heartbeatInterval) {
|
|
87
|
+
clearInterval(self._heartbeatInterval);
|
|
88
|
+
self._heartbeatInterval = null;
|
|
89
|
+
}
|
|
90
|
+
Usion.log('Game socket disconnected: ' + reason);
|
|
91
|
+
if (self._eventHandlers.disconnect) {
|
|
92
|
+
self._eventHandlers.disconnect(reason);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
self.socket.on('reconnect', function(attemptNumber) {
|
|
97
|
+
Usion.log('Game socket reconnected after ' + attemptNumber + ' attempts');
|
|
98
|
+
if (self._eventHandlers.reconnect) {
|
|
99
|
+
self._eventHandlers.reconnect(attemptNumber);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Game event handlers
|
|
104
|
+
self.socket.on('game:joined', function(data) {
|
|
105
|
+
if (data.sequence !== undefined) {
|
|
106
|
+
self._lastSequence = data.sequence;
|
|
107
|
+
}
|
|
108
|
+
if (self._eventHandlers.joined) {
|
|
109
|
+
self._eventHandlers.joined(data);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
self.socket.on('game:player_joined', function(data) {
|
|
114
|
+
if (self._eventHandlers.playerJoined) {
|
|
115
|
+
self._eventHandlers.playerJoined(data);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
self.socket.on('game:player_left', function(data) {
|
|
120
|
+
if (self._eventHandlers.playerLeft) {
|
|
121
|
+
self._eventHandlers.playerLeft(data);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
self.socket.on('game:state', function(data) {
|
|
126
|
+
if (data.sequence !== undefined) {
|
|
127
|
+
self._lastSequence = Math.max(self._lastSequence, data.sequence);
|
|
128
|
+
}
|
|
129
|
+
if (self._eventHandlers.stateUpdate) {
|
|
130
|
+
self._eventHandlers.stateUpdate(data);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
self.socket.on('game:sync', function(data) {
|
|
135
|
+
if (data.sequence !== undefined) {
|
|
136
|
+
self._lastSequence = data.sequence;
|
|
137
|
+
}
|
|
138
|
+
if (self._eventHandlers.sync) {
|
|
139
|
+
self._eventHandlers.sync(data);
|
|
140
|
+
}
|
|
141
|
+
// Also trigger stateUpdate for backwards compat
|
|
142
|
+
if (self._eventHandlers.stateUpdate) {
|
|
143
|
+
self._eventHandlers.stateUpdate(data);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
self.socket.on('game:action', function(data) {
|
|
148
|
+
if (data.sequence !== undefined) {
|
|
149
|
+
self._lastSequence = Math.max(self._lastSequence, data.sequence);
|
|
150
|
+
}
|
|
151
|
+
if (self._eventHandlers.action) {
|
|
152
|
+
self._eventHandlers.action(data);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
self.socket.on('game:realtime', function(data) {
|
|
157
|
+
if (self._eventHandlers.realtime) {
|
|
158
|
+
self._eventHandlers.realtime(data);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
self.socket.on('game:finished', function(data) {
|
|
163
|
+
if (data.sequence !== undefined) {
|
|
164
|
+
self._lastSequence = data.sequence;
|
|
165
|
+
}
|
|
166
|
+
if (self._eventHandlers.finished) {
|
|
167
|
+
self._eventHandlers.finished(data);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
self.socket.on('game:error', function(data) {
|
|
172
|
+
Usion.log('Game error: ' + (data.message || data.code));
|
|
173
|
+
if (self._eventHandlers.error) {
|
|
174
|
+
self._eventHandlers.error(data);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
self.socket.on('game:rematch_request', function(data) {
|
|
179
|
+
if (self._eventHandlers.rematchRequest) {
|
|
180
|
+
self._eventHandlers.rematchRequest(data);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
self.socket.on('game:restarted', function(data) {
|
|
185
|
+
self._lastSequence = 0; // Reset sequence on rematch
|
|
186
|
+
if (self._eventHandlers.restarted) {
|
|
187
|
+
self._eventHandlers.restarted(data);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
} catch (err) {
|
|
192
|
+
reject(err);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion Mini App SDK v2.1
|
|
3
|
+
*
|
|
4
|
+
* JavaScript utilities for Mini Apps (Iframe Games & Services)
|
|
5
|
+
* Import via: <script src="https://usions.com/usion-sdk.js"></script>
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - User info and authentication
|
|
9
|
+
* - Persistent storage (per-user, per-service)
|
|
10
|
+
* - Wallet/payment integration
|
|
11
|
+
* - Session management
|
|
12
|
+
* - Real-time game support via Socket.IO
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { core } from './core.js';
|
|
16
|
+
import { createUserModule } from './user.js';
|
|
17
|
+
import { createStorageModule } from './storage.js';
|
|
18
|
+
import { createWalletModule } from './wallet.js';
|
|
19
|
+
import { createSessionModule } from './session.js';
|
|
20
|
+
import { createChatModule } from './chat.js';
|
|
21
|
+
import { createBotModule } from './bot.js';
|
|
22
|
+
import { createResultsMethods } from './results.js';
|
|
23
|
+
import { createBackButtonMethods } from './back-button.js';
|
|
24
|
+
import { uiMethods } from './ui.js';
|
|
25
|
+
import { miscMethods } from './misc.js';
|
|
26
|
+
import { createFileStorageModule } from './file-storage.js';
|
|
27
|
+
import { createGameModule } from './game-core.js';
|
|
28
|
+
|
|
29
|
+
// Build the Usion object from core
|
|
30
|
+
const Usion = Object.assign({}, core);
|
|
31
|
+
|
|
32
|
+
// Attach sub-modules (these reference Usion internally)
|
|
33
|
+
Usion.user = createUserModule(Usion);
|
|
34
|
+
Usion.storage = createStorageModule(Usion);
|
|
35
|
+
Usion.wallet = createWalletModule(Usion);
|
|
36
|
+
Usion.session = createSessionModule(Usion);
|
|
37
|
+
Usion.chat = createChatModule(Usion);
|
|
38
|
+
Usion.bot = createBotModule(Usion);
|
|
39
|
+
Usion.fileStorage = createFileStorageModule(Usion);
|
|
40
|
+
Usion.game = createGameModule(Usion);
|
|
41
|
+
|
|
42
|
+
// Attach results methods directly on Usion
|
|
43
|
+
Object.assign(Usion, createResultsMethods(Usion));
|
|
44
|
+
|
|
45
|
+
// Attach back button methods directly on Usion
|
|
46
|
+
Object.assign(Usion, createBackButtonMethods(Usion));
|
|
47
|
+
|
|
48
|
+
// Attach UI utilities directly on Usion
|
|
49
|
+
Object.assign(Usion, uiMethods);
|
|
50
|
+
|
|
51
|
+
// Attach misc methods (submit, error, exit, share, log, on, requestPayment)
|
|
52
|
+
Object.assign(Usion, miscMethods);
|
|
53
|
+
|
|
54
|
+
export default Usion;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK Misc — submit, error, exit, share, log, on, requestPayment (legacy)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const miscMethods = {
|
|
6
|
+
/**
|
|
7
|
+
* Request payment from user (legacy method)
|
|
8
|
+
* @deprecated Use Usion.wallet.requestPayment instead
|
|
9
|
+
*/
|
|
10
|
+
requestPayment: function(amount, reason, data) {
|
|
11
|
+
return this.wallet.requestPayment(amount, reason, data);
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Submit result and signal completion
|
|
16
|
+
* @param {object} data - Result data to send to parent
|
|
17
|
+
*/
|
|
18
|
+
submit: function(data) {
|
|
19
|
+
this._post({
|
|
20
|
+
type: 'SUBMIT',
|
|
21
|
+
data: data
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Report an error to parent app
|
|
27
|
+
* @param {string} message - Error message
|
|
28
|
+
*/
|
|
29
|
+
error: function(message) {
|
|
30
|
+
this._post({
|
|
31
|
+
type: 'ERROR',
|
|
32
|
+
message: message
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Request to close the mini app
|
|
38
|
+
* @param {object} [options] - Optional exit options
|
|
39
|
+
* @param {number} [options.backCount] - Number of screens to go back (default 1)
|
|
40
|
+
*/
|
|
41
|
+
exit: function(options) {
|
|
42
|
+
var msg = { type: 'EXIT' };
|
|
43
|
+
if (options && typeof options.backCount === 'number' && options.backCount > 1) {
|
|
44
|
+
msg.backCount = options.backCount;
|
|
45
|
+
}
|
|
46
|
+
this._post(msg);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Share content through the app's native share and optionally post to Usions feed
|
|
51
|
+
*
|
|
52
|
+
* @param {string} contentType - Type of content: 'audio' | 'image' | 'video' | 'text' | 'mixed'
|
|
53
|
+
* @param {object} data - Content data to share:
|
|
54
|
+
* - text: Optional text/caption for the post
|
|
55
|
+
* - audioUrl: URL for audio content (when contentType is 'audio')
|
|
56
|
+
* - imageUrl: URL for image content (when contentType is 'image')
|
|
57
|
+
* - videoUrl: URL for video content (when contentType is 'video')
|
|
58
|
+
* - thumbnailUrl: Optional thumbnail URL for video/audio
|
|
59
|
+
* - width: Optional width for image/video
|
|
60
|
+
* - height: Optional height for image/video
|
|
61
|
+
* - duration: Optional duration in seconds for audio/video
|
|
62
|
+
* - media: Array of media items for 'mixed' content type
|
|
63
|
+
* - Each item: { type: 'image'|'video'|'audio', url: string, thumbnailUrl?, width?, height?, duration? }
|
|
64
|
+
*/
|
|
65
|
+
share: function(contentType, data) {
|
|
66
|
+
var shareData = Object.assign({}, data, {
|
|
67
|
+
contentType: contentType,
|
|
68
|
+
serviceId: this.config.serviceId,
|
|
69
|
+
serviceName: this.config.serviceName
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this._post({
|
|
73
|
+
type: 'SHARE',
|
|
74
|
+
contentType: contentType,
|
|
75
|
+
data: shareData
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Download a file to the device's storage / gallery.
|
|
81
|
+
* @param {string} url - URL of the file to download
|
|
82
|
+
* @param {string} [filename] - Optional filename (default: 'download.mp4')
|
|
83
|
+
* @returns {Promise<{success: boolean, error?: string}>}
|
|
84
|
+
*/
|
|
85
|
+
download: function(url, filename) {
|
|
86
|
+
return this._request('DOWNLOAD_FILE', {
|
|
87
|
+
url: url,
|
|
88
|
+
filename: filename || 'download.mp4'
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Log message to native console (for debugging)
|
|
94
|
+
* @param {string} msg - Message to log
|
|
95
|
+
*/
|
|
96
|
+
log: function(msg) {
|
|
97
|
+
this._post({
|
|
98
|
+
type: 'LOG',
|
|
99
|
+
msg: msg
|
|
100
|
+
});
|
|
101
|
+
console.log('[Usion]', msg);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Listen for messages from parent app
|
|
106
|
+
* @param {string} type - Message type to listen for
|
|
107
|
+
* @param {function} callback - Handler function
|
|
108
|
+
*/
|
|
109
|
+
on: function(type, callback) {
|
|
110
|
+
window.addEventListener('message', function(event) {
|
|
111
|
+
let data;
|
|
112
|
+
try {
|
|
113
|
+
data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (data.type === type) {
|
|
119
|
+
callback(data);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK Results — server-side result persistence across devices
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function createResultsMethods(Usion) {
|
|
6
|
+
return {
|
|
7
|
+
/**
|
|
8
|
+
* Save a result to server-side storage (persists across devices).
|
|
9
|
+
* @param {string} data - Result string (URL, JSON, etc.)
|
|
10
|
+
* @param {object} [metadata] - Optional metadata (thumbnail_url, title, type)
|
|
11
|
+
* @returns {Promise<object>} The saved result document
|
|
12
|
+
*/
|
|
13
|
+
saveResult: function(data, metadata) {
|
|
14
|
+
return Usion._request('SAVE_RESULT', { data: data, metadata: metadata || {} }, 15000);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Delete a saved result by ID.
|
|
19
|
+
* @param {string} resultId - The result ID to delete
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*/
|
|
22
|
+
deleteResult: function(resultId) {
|
|
23
|
+
return Usion._request('DELETE_RESULT', { resultId: resultId });
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get all saved results for this service (populated from INIT config).
|
|
28
|
+
* @returns {Array} Array of result objects
|
|
29
|
+
*/
|
|
30
|
+
getResults: function() {
|
|
31
|
+
return Usion._results || [];
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK Session Module — ephemeral session data management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} Usion - Reference to the main Usion object
|
|
7
|
+
*/
|
|
8
|
+
export function createSessionModule(Usion) {
|
|
9
|
+
return {
|
|
10
|
+
_id: null,
|
|
11
|
+
_data: {},
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the current session ID
|
|
15
|
+
* @returns {string|null}
|
|
16
|
+
*/
|
|
17
|
+
getId: function() {
|
|
18
|
+
return this._id || Usion.config.sessionId || null;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get session data
|
|
23
|
+
* @param {string} key - Optional key to get specific value
|
|
24
|
+
* @returns {any} Session data or specific value
|
|
25
|
+
*/
|
|
26
|
+
getData: function(key) {
|
|
27
|
+
if (key) {
|
|
28
|
+
return this._data[key];
|
|
29
|
+
}
|
|
30
|
+
return this._data;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Set session data (ephemeral, cleared on session end)
|
|
35
|
+
* @param {string|object} keyOrData - Key or object of data to set
|
|
36
|
+
* @param {any} value - Value if key is string
|
|
37
|
+
*/
|
|
38
|
+
setData: function(keyOrData, value) {
|
|
39
|
+
if (typeof keyOrData === 'object') {
|
|
40
|
+
Object.assign(this._data, keyOrData);
|
|
41
|
+
} else {
|
|
42
|
+
this._data[keyOrData] = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Notify parent of session data change
|
|
46
|
+
Usion._post({
|
|
47
|
+
type: 'SESSION_DATA_UPDATE',
|
|
48
|
+
data: this._data
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Clear session data
|
|
54
|
+
*/
|
|
55
|
+
clear: function() {
|
|
56
|
+
this._data = {};
|
|
57
|
+
Usion._post({
|
|
58
|
+
type: 'SESSION_DATA_CLEAR'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK Storage Module — persistent storage (per-user, per-service)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} Usion - Reference to the main Usion object
|
|
7
|
+
*/
|
|
8
|
+
export function createStorageModule(Usion) {
|
|
9
|
+
return {
|
|
10
|
+
/**
|
|
11
|
+
* Get a stored value
|
|
12
|
+
* @param {string} key - Storage key
|
|
13
|
+
* @returns {Promise<any>} Stored value or null
|
|
14
|
+
*/
|
|
15
|
+
get: function(key) {
|
|
16
|
+
return Usion._request('STORAGE_GET', { key: key }).then(function(response) {
|
|
17
|
+
return response.value;
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Set a stored value
|
|
23
|
+
* @param {string} key - Storage key
|
|
24
|
+
* @param {any} value - Value to store (will be JSON serialized)
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
set: function(key, value) {
|
|
28
|
+
return Usion._request('STORAGE_SET', { key: key, value: value }).then(function() {
|
|
29
|
+
return;
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Remove a stored value
|
|
35
|
+
* @param {string} key - Storage key
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
remove: function(key) {
|
|
39
|
+
return Usion._request('STORAGE_REMOVE', { key: key }).then(function() {
|
|
40
|
+
return;
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Clear all stored values for this service
|
|
46
|
+
* @returns {Promise<void>}
|
|
47
|
+
*/
|
|
48
|
+
clear: function() {
|
|
49
|
+
return Usion._request('STORAGE_CLEAR', {}).then(function() {
|
|
50
|
+
return;
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get all keys
|
|
56
|
+
* @returns {Promise<string[]>}
|
|
57
|
+
*/
|
|
58
|
+
keys: function() {
|
|
59
|
+
return Usion._request('STORAGE_KEYS', {}).then(function(response) {
|
|
60
|
+
return response.keys || [];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK UI Utilities — DOM helpers for mini apps
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const uiMethods = {
|
|
6
|
+
/**
|
|
7
|
+
* Set button to loading state
|
|
8
|
+
* @param {HTMLElement|string} btn - Button element or selector
|
|
9
|
+
* @param {boolean} loading - Whether to show loading state
|
|
10
|
+
*/
|
|
11
|
+
setLoading: function(btn, loading) {
|
|
12
|
+
const el = typeof btn === 'string' ? document.querySelector(btn) : btn;
|
|
13
|
+
if (!el) return;
|
|
14
|
+
|
|
15
|
+
if (loading) {
|
|
16
|
+
el.classList.add('usion-btn-loading');
|
|
17
|
+
el.disabled = true;
|
|
18
|
+
el.dataset.originalText = el.textContent;
|
|
19
|
+
} else {
|
|
20
|
+
el.classList.remove('usion-btn-loading');
|
|
21
|
+
el.disabled = false;
|
|
22
|
+
if (el.dataset.originalText) {
|
|
23
|
+
el.textContent = el.dataset.originalText;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Show/hide an element
|
|
30
|
+
* @param {HTMLElement|string} el - Element or selector
|
|
31
|
+
* @param {boolean} show - Whether to show or hide
|
|
32
|
+
*/
|
|
33
|
+
toggle: function(el, show) {
|
|
34
|
+
const element = typeof el === 'string' ? document.querySelector(el) : el;
|
|
35
|
+
if (!element) return;
|
|
36
|
+
|
|
37
|
+
if (show) {
|
|
38
|
+
element.classList.remove('usion-hidden', 'hidden');
|
|
39
|
+
element.classList.add('usion-visible');
|
|
40
|
+
} else {
|
|
41
|
+
element.classList.add('usion-hidden');
|
|
42
|
+
element.classList.remove('usion-visible');
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Update character count display
|
|
48
|
+
* @param {HTMLElement|string} input - Input element or selector
|
|
49
|
+
* @param {HTMLElement|string} counter - Counter element or selector
|
|
50
|
+
* @param {number} max - Maximum characters
|
|
51
|
+
*/
|
|
52
|
+
charCount: function(input, counter, max) {
|
|
53
|
+
const inputEl = typeof input === 'string' ? document.querySelector(input) : input;
|
|
54
|
+
const counterEl = typeof counter === 'string' ? document.querySelector(counter) : counter;
|
|
55
|
+
|
|
56
|
+
if (!inputEl || !counterEl) return;
|
|
57
|
+
|
|
58
|
+
function update() {
|
|
59
|
+
const count = inputEl.value.length;
|
|
60
|
+
counterEl.textContent = count + ' / ' + max;
|
|
61
|
+
|
|
62
|
+
counterEl.classList.remove('warning', 'error');
|
|
63
|
+
if (count > max * 0.9) {
|
|
64
|
+
counterEl.classList.add('error');
|
|
65
|
+
} else if (count > max * 0.7) {
|
|
66
|
+
counterEl.classList.add('warning');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
inputEl.addEventListener('input', update);
|
|
71
|
+
update();
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a selection handler for grid items
|
|
76
|
+
* @param {string} containerSelector - Container selector
|
|
77
|
+
* @param {string} itemSelector - Item selector
|
|
78
|
+
* @param {function} onChange - Callback when selection changes
|
|
79
|
+
*/
|
|
80
|
+
selectionGrid: function(containerSelector, itemSelector, onChange) {
|
|
81
|
+
const container = document.querySelector(containerSelector);
|
|
82
|
+
if (!container) return;
|
|
83
|
+
|
|
84
|
+
let selected = null;
|
|
85
|
+
|
|
86
|
+
container.querySelectorAll(itemSelector).forEach(function(item) {
|
|
87
|
+
item.addEventListener('click', function() {
|
|
88
|
+
// Remove selection from all
|
|
89
|
+
container.querySelectorAll(itemSelector).forEach(function(i) {
|
|
90
|
+
i.classList.remove('selected');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Select this one
|
|
94
|
+
item.classList.add('selected');
|
|
95
|
+
selected = item.dataset.value || item.dataset.id;
|
|
96
|
+
|
|
97
|
+
if (onChange) onChange(selected, item);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
getSelected: function() { return selected; },
|
|
103
|
+
clear: function() {
|
|
104
|
+
container.querySelectorAll(itemSelector).forEach(function(i) {
|
|
105
|
+
i.classList.remove('selected');
|
|
106
|
+
});
|
|
107
|
+
selected = null;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK User Module — user info and authentication
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} Usion - Reference to the main Usion object
|
|
7
|
+
*/
|
|
8
|
+
export function createUserModule(Usion) {
|
|
9
|
+
return {
|
|
10
|
+
_id: null,
|
|
11
|
+
_name: null,
|
|
12
|
+
_avatar: null,
|
|
13
|
+
_token: null,
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the current user's ID
|
|
17
|
+
* @returns {string|null}
|
|
18
|
+
*/
|
|
19
|
+
getId: function() {
|
|
20
|
+
return this._id || Usion.config.userId || null;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the current user's display name
|
|
25
|
+
* @returns {string|null}
|
|
26
|
+
*/
|
|
27
|
+
getName: function() {
|
|
28
|
+
return this._name || Usion.config.userName || null;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the current user's avatar URL
|
|
33
|
+
* @returns {string|null}
|
|
34
|
+
*/
|
|
35
|
+
getAvatar: function() {
|
|
36
|
+
return this._avatar || Usion.config.userAvatar || null;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the user's auth token for socket connections
|
|
41
|
+
* @returns {string|null}
|
|
42
|
+
*/
|
|
43
|
+
getToken: function() {
|
|
44
|
+
return this._token || Usion.config.authToken || null;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get full user profile
|
|
49
|
+
* @returns {Promise<object>} User profile with id, name, avatar
|
|
50
|
+
*/
|
|
51
|
+
getProfile: function() {
|
|
52
|
+
return Usion._request('GET_USER_PROFILE', {}).then(function(response) {
|
|
53
|
+
return response.profile || {
|
|
54
|
+
id: Usion.user.getId(),
|
|
55
|
+
name: Usion.user.getName(),
|
|
56
|
+
avatar: Usion.user.getAvatar()
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|