@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/src/index.js CHANGED
@@ -1,55 +1,10 @@
1
1
  /**
2
- * @usion/sdk — ES module wrapper for the Usion Mini App SDK
2
+ * @usions/sdk — ES module entry point for the Usion Mini App SDK
3
3
  *
4
4
  * Usage:
5
- * import Usion from '@usion/sdk';
5
+ * import Usion from '@usions/sdk';
6
6
  * // or
7
- * import { Usion } from '@usion/sdk';
7
+ * import { Usion } from '@usions/sdk';
8
8
  */
9
9
 
10
- // Create a synthetic global for the IIFE to attach to
11
- const _global = {};
12
-
13
- // Execute the IIFE SDK with our synthetic global
14
- (function (global) {
15
- 'use strict';
16
-
17
- // ---- Begin inlined SDK bootstrap ----
18
- // We re-export the Usion object that the IIFE creates.
19
- // Rather than duplicating the full SDK source, we dynamically load it.
20
- // For the npm module path, consumers import this file;
21
- // for script-tag usage, they load browser.js directly.
22
- // ---- End inlined SDK bootstrap ----
23
-
24
- // Attach a marker so consumers can detect the module version
25
- global.__USION_SDK_MODULE__ = true;
26
- })(_global);
27
-
28
- // Dynamic import approach: read the IIFE SDK and evaluate it
29
- import { readFileSync } from 'fs';
30
- import { fileURLToPath } from 'url';
31
- import { dirname, join } from 'path';
32
-
33
- const __filename = fileURLToPath(import.meta.url);
34
- const __dirname = dirname(__filename);
35
-
36
- // Create a minimal global-like object for the IIFE
37
- const _sdkGlobal = {};
38
- const sdkSource = readFileSync(join(__dirname, 'browser.js'), 'utf-8');
39
-
40
- // Replace the IIFE's global reference to capture the Usion object
41
- const wrappedSource = sdkSource
42
- .replace(
43
- /\}\)\(typeof window !== 'undefined' \? window : this\);?\s*$/,
44
- '})(_sdkGlobal);'
45
- );
46
-
47
- // Execute in a function scope to avoid polluting
48
- const execFn = new Function('_sdkGlobal', wrappedSource);
49
- execFn(_sdkGlobal);
50
-
51
- /** The Usion SDK instance */
52
- const Usion = _sdkGlobal.Usion;
53
-
54
- export { Usion };
55
- export default Usion;
10
+ export { default as Usion, default } from './modules/index.js';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Usion SDK Back Button — claim/release host back button for in-app navigation
3
+ */
4
+
5
+ export function createBackButtonMethods(Usion) {
6
+ return {
7
+ /**
8
+ * Claim the host app's back button for one-time in-app navigation.
9
+ * When claimed, pressing back sends BACK_BUTTON_PRESSED to the mini app
10
+ * instead of closing it. Automatically resets after one press.
11
+ * @param {function} callback - Called when the user presses the claimed back button
12
+ */
13
+ claimBackButton: function(callback) {
14
+ Usion._backButtonCallback = callback;
15
+ Usion._post({ type: 'CLAIM_BACK_BUTTON' });
16
+ },
17
+
18
+ /**
19
+ * Release a previously claimed back button, restoring default close behavior.
20
+ */
21
+ releaseBackButton: function() {
22
+ Usion._backButtonCallback = null;
23
+ Usion._post({ type: 'RELEASE_BACK_BUTTON' });
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Usion SDK Bot Bridge — for inline bot iframes
3
+ */
4
+
5
+ export function createBotModule(Usion) {
6
+ return {
7
+ _messageHandler: null,
8
+
9
+ /**
10
+ * Call a bot action. Delivered as an "iframe_action" webhook to the bot's server.
11
+ * @param {string} action - Action name (e.g., "submit_form", "select_item")
12
+ * @param {object} [data] - Action payload
13
+ * @returns {Promise} Resolves when the host app confirms delivery
14
+ */
15
+ callAction: function(action, data) {
16
+ return Usion._request('CALL_BOT', { action: action, data: data || {} }, 30000);
17
+ },
18
+
19
+ /**
20
+ * Send a message as if the user typed it. Triggers the normal bot webhook flow.
21
+ * @param {string} text - Message text
22
+ */
23
+ sendMessage: function(text) {
24
+ Usion._post({ type: 'SEND_USER_MESSAGE', text: text });
25
+ },
26
+
27
+ /**
28
+ * Update context metadata visible to the bot on the next webhook delivery.
29
+ * @param {object} ctx - Context key/value pairs
30
+ */
31
+ updateContext: function(ctx) {
32
+ Usion._post({ type: 'UPDATE_CONTEXT', context: ctx });
33
+ },
34
+
35
+ /**
36
+ * Close the iframe, optionally returning a result to the bot.
37
+ * @param {object} [result] - Optional result data
38
+ */
39
+ close: function(result) {
40
+ Usion._post({ type: 'CLOSE', result: result });
41
+ },
42
+
43
+ /**
44
+ * Listen for new bot messages in the conversation.
45
+ * Called whenever the bot sends a message (text, components, etc.).
46
+ * @param {function} callback - Called with { id, content, content_type, components, sender_id }
47
+ */
48
+ onMessage: function(callback) {
49
+ this._messageHandler = callback;
50
+ }
51
+ };
52
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Usion SDK Chat Module — messaging between users
3
+ */
4
+
5
+ /**
6
+ * @param {object} Usion - Reference to the main Usion object
7
+ */
8
+ export function createChatModule(Usion) {
9
+ return {
10
+ /**
11
+ * Request to send a message to another user.
12
+ * The parent app will show a confirmation prompt to the user.
13
+ * @param {string} recipientId - Usion user ID of the recipient
14
+ * @param {string} message - Message content to send
15
+ * @returns {Promise<{success: boolean, reason?: string}>}
16
+ */
17
+ sendMessage: function(recipientId, message) {
18
+ return Usion._request('SEND_MESSAGE_REQUEST', {
19
+ recipientId: recipientId,
20
+ message: message
21
+ });
22
+ },
23
+
24
+ /**
25
+ * Create a personal chat with another user (no message sent).
26
+ * @param {string} peerUserId - Usion user ID of the other user
27
+ * @returns {Promise<{chatId: string, peerName: string, peerUsername: string, peerAvatar: string}>}
28
+ */
29
+ createPersonalChat: function(peerUserId) {
30
+ return Usion._request('CREATE_PERSONAL_CHAT', {
31
+ peerUserId: peerUserId
32
+ });
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Usion SDK Core — init, _post, _request, message handling
3
+ */
4
+
5
+ // Request ID counter for tracking async responses
6
+ let _requestId = 0;
7
+ export const _pendingRequests = {};
8
+
9
+ export function getNextRequestId() {
10
+ return ++_requestId;
11
+ }
12
+
13
+ /**
14
+ * Core Usion object with init, _post, _request
15
+ */
16
+ export const core = {
17
+ version: '2.1.0',
18
+ config: {},
19
+ _initialized: false,
20
+ _initCallback: null,
21
+ _messageHandlerRegistered: false,
22
+ _results: [],
23
+ _backButtonCallback: null,
24
+
25
+ /**
26
+ * Initialize the SDK with config from parent app
27
+ * @param {function} callback - Called with config when ready
28
+ */
29
+ init: function(callback) {
30
+ const self = this;
31
+
32
+ // Prevent double initialization - just update callback
33
+ if (self._initialized) {
34
+ if (callback) callback(self.config);
35
+ return;
36
+ }
37
+
38
+ // Store callback for when config arrives
39
+ self._initCallback = callback;
40
+
41
+ // Only register message handler once
42
+ if (self._messageHandlerRegistered) {
43
+ return;
44
+ }
45
+ self._messageHandlerRegistered = true;
46
+
47
+ // Setup global message handler
48
+ window.addEventListener('message', function(event) {
49
+ let data;
50
+ try {
51
+ data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
52
+ } catch (e) {
53
+ return;
54
+ }
55
+
56
+ // Handle INIT message
57
+ if (data.type === 'INIT' && data.config) {
58
+ // Prevent double config - only set once
59
+ if (self._initialized) {
60
+ return;
61
+ }
62
+
63
+ self.config = data.config;
64
+ self._initialized = true;
65
+ // We received INIT from a parent -> we are embedded (iframe or WebView)
66
+ self._isEmbedded = true;
67
+
68
+ // Initialize user module with config data
69
+ if (data.config.userId) {
70
+ self.user._id = data.config.userId;
71
+ self.user._name = data.config.userName;
72
+ self.user._avatar = data.config.userAvatar;
73
+ self.user._token = data.config.authToken;
74
+ }
75
+
76
+ // Initialize session module
77
+ if (data.config.sessionId) {
78
+ self.session._id = data.config.sessionId;
79
+ self.session._data = data.config.sessionData || {};
80
+ }
81
+
82
+ // Initialize wallet with balance if provided
83
+ if (data.config.balance !== undefined) {
84
+ self.wallet._balance = data.config.balance;
85
+ }
86
+
87
+ // Initialize results from server
88
+ if (data.config.results) {
89
+ self._results = data.config.results;
90
+ }
91
+
92
+ // Call the stored init callback
93
+ if (self._initCallback) {
94
+ self._initCallback(data.config);
95
+ }
96
+ }
97
+
98
+ // Handle response messages for async requests
99
+ if (data._requestId && _pendingRequests[data._requestId]) {
100
+ const { resolve, reject } = _pendingRequests[data._requestId];
101
+ delete _pendingRequests[data._requestId];
102
+
103
+ if (data.error) {
104
+ reject(new Error(data.error));
105
+ } else {
106
+ resolve(data);
107
+ }
108
+ }
109
+
110
+ // Handle balance updates
111
+ if (data.type === 'BALANCE_UPDATE') {
112
+ self.wallet._balance = data.balance;
113
+ if (self.wallet._balanceChangeHandler) {
114
+ self.wallet._balanceChangeHandler(data.balance);
115
+ }
116
+ }
117
+
118
+ // Handle back button pressed (one-time claim)
119
+ if (data.type === 'BACK_BUTTON_PRESSED' && self._backButtonCallback) {
120
+ var cb = self._backButtonCallback;
121
+ self._backButtonCallback = null; // one-time use
122
+ cb();
123
+ }
124
+
125
+ // Handle bot messages forwarded from host app
126
+ if (data.type === 'BOT_MESSAGE' && self.bot && self.bot._messageHandler) {
127
+ self.bot._messageHandler(data.message);
128
+ }
129
+ });
130
+
131
+ // Signal ready to parent
132
+ this._post({ type: 'READY' });
133
+ },
134
+
135
+ /**
136
+ * Get the current theme ('light' or 'dark')
137
+ * @returns {string}
138
+ */
139
+ getTheme: function() {
140
+ return this.config.theme || 'light';
141
+ },
142
+
143
+ /**
144
+ * Get the current language/locale (e.g. 'en', 'mn')
145
+ * @returns {string}
146
+ */
147
+ getLanguage: function() {
148
+ return this.config.language || 'en';
149
+ },
150
+
151
+ /**
152
+ * Send message to parent app
153
+ * @private
154
+ */
155
+ _post: function(message) {
156
+ const msg = JSON.stringify(message);
157
+
158
+ // React Native WebView
159
+ if (window.ReactNativeWebView) {
160
+ window.ReactNativeWebView.postMessage(msg);
161
+ return;
162
+ }
163
+
164
+ // Web iframe
165
+ if (window.parent !== window) {
166
+ window.parent.postMessage(message, '*');
167
+ }
168
+ },
169
+
170
+ /**
171
+ * Send async request to parent and wait for response
172
+ * @private
173
+ */
174
+ _request: function(type, data, timeout) {
175
+ const self = this;
176
+ timeout = timeout || 5000;
177
+
178
+ return new Promise(function(resolve, reject) {
179
+ const requestId = getNextRequestId();
180
+
181
+ // Setup timeout
182
+ const timer = setTimeout(function() {
183
+ delete _pendingRequests[requestId];
184
+ reject(new Error('Request timeout'));
185
+ }, timeout);
186
+
187
+ // Store pending request
188
+ _pendingRequests[requestId] = {
189
+ resolve: function(result) {
190
+ clearTimeout(timer);
191
+ resolve(result);
192
+ },
193
+ reject: function(error) {
194
+ clearTimeout(timer);
195
+ reject(error);
196
+ }
197
+ };
198
+
199
+ // Send request
200
+ self._post({
201
+ type: type,
202
+ _requestId: requestId,
203
+ ...data
204
+ });
205
+ });
206
+ }
207
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Usion SDK File Storage — large binary data (IndexedDB / filesystem)
3
+ *
4
+ * Scoped per-user, per-service like regular storage.
5
+ * Uses IndexedDB (web) or filesystem (mobile) — no localStorage size limits.
6
+ */
7
+
8
+ export function createFileStorageModule(sdk) {
9
+ return {
10
+ /**
11
+ * Store a file (base64 encoded)
12
+ * @param {string} key - Storage key
13
+ * @param {string} base64Data - Base64-encoded file content (no data: prefix)
14
+ * @param {string} mimeType - MIME type (e.g. 'image/png')
15
+ * @returns {Promise<void>}
16
+ */
17
+ set: function(key, base64Data, mimeType) {
18
+ return sdk._request('FILE_STORAGE_SET', {
19
+ key: key,
20
+ base64Data: base64Data,
21
+ mimeType: mimeType || 'application/octet-stream'
22
+ }, 30000).then(function() { return; });
23
+ },
24
+
25
+ /**
26
+ * Get a stored file
27
+ * @param {string} key - Storage key
28
+ * @returns {Promise<{base64Data: string, mimeType: string} | null>}
29
+ */
30
+ get: function(key) {
31
+ return sdk._request('FILE_STORAGE_GET', { key: key }, 30000).then(function(response) {
32
+ if (!response || !response.base64Data) return null;
33
+ return { base64Data: response.base64Data, mimeType: response.mimeType };
34
+ });
35
+ },
36
+
37
+ /**
38
+ * Remove a stored file
39
+ * @param {string} key - Storage key
40
+ * @returns {Promise<void>}
41
+ */
42
+ remove: function(key) {
43
+ return sdk._request('FILE_STORAGE_REMOVE', { key: key }).then(function() { return; });
44
+ }
45
+ };
46
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Usion SDK Game Core — game module base, connect routing, event registrations
3
+ */
4
+
5
+ import { applyGameDirect } from './game-direct.js';
6
+ import { applyGameSocket } from './game-socket.js';
7
+ import { applyGameProxy } from './game-proxy.js';
8
+ import { applyGameMethods } from './game-methods.js';
9
+
10
+ /**
11
+ * Create the game module with all sub-modules applied
12
+ * @param {object} Usion - Reference to the main Usion object
13
+ */
14
+ export function createGameModule(Usion) {
15
+ const game = {
16
+ socket: null,
17
+ directSocket: null,
18
+ roomId: null,
19
+ playerId: null,
20
+ connected: false,
21
+ directMode: false,
22
+ directConfig: null,
23
+ _directSeq: 0,
24
+ _eventHandlers: {},
25
+ _lastSequence: 0,
26
+ _connecting: false,
27
+ _connectPromise: null,
28
+ _joined: false,
29
+ _joinPromise: null,
30
+ _useProxy: false,
31
+ _proxyListenerSetup: false,
32
+ _heartbeatInterval: null,
33
+
34
+ /**
35
+ * Connect to the game socket server
36
+ * @param {string} socketUrl - Socket.IO server URL (optional, uses config)
37
+ * @param {string} token - JWT auth token (optional, uses user.getToken())
38
+ * @returns {Promise} Resolves when connected
39
+ */
40
+ connect: function(socketUrl, token) {
41
+ const self = this;
42
+ var connectionMode = (Usion.config && Usion.config.connectionMode) || 'platform';
43
+ if (connectionMode === 'direct') {
44
+ return self.connectDirect();
45
+ }
46
+
47
+ // Use config values as defaults
48
+ socketUrl = socketUrl || Usion.config.socketUrl;
49
+ token = token || Usion.user.getToken();
50
+
51
+ if (!socketUrl) {
52
+ return Promise.reject(new Error('No socket URL provided'));
53
+ }
54
+ if (!token) {
55
+ return Promise.reject(new Error('No auth token available'));
56
+ }
57
+
58
+ // If already connected (direct or proxy), return immediately
59
+ if (self._useProxy && self.connected) {
60
+ return Promise.resolve();
61
+ }
62
+ if (self.socket && self.connected) {
63
+ return Promise.resolve();
64
+ }
65
+
66
+ // If currently connecting, return the existing promise
67
+ if (self._connecting && self._connectPromise) {
68
+ return self._connectPromise;
69
+ }
70
+
71
+ // When running inside an iframe or WebView, use parent as socket proxy
72
+ var isInFrame = !!window.__USION_PROXY__
73
+ || window.parent !== window
74
+ || !!window.ReactNativeWebView
75
+ || !!Usion._isEmbedded;
76
+
77
+ if (isInFrame) {
78
+ Usion.log('Running in iframe \u2013 using parent app as socket proxy');
79
+ return self._connectViaProxy();
80
+ }
81
+
82
+ self._connecting = true;
83
+ self._connectPromise = new Promise(function(resolve, reject) {
84
+ // Check if socket.io-client is available
85
+ if (typeof io === 'undefined') {
86
+ // Load socket.io client
87
+ var script = document.createElement('script');
88
+ script.src = '/socket.io.min.js';
89
+ script.onload = function() {
90
+ self._initSocket(socketUrl, token, resolve, reject);
91
+ };
92
+ script.onerror = function() {
93
+ // Local file not available, try CDN as fallback
94
+ var cdnScript = document.createElement('script');
95
+ cdnScript.src = 'https://cdn.socket.io/4.7.2/socket.io.min.js';
96
+ cdnScript.onload = function() {
97
+ self._initSocket(socketUrl, token, resolve, reject);
98
+ };
99
+ cdnScript.onerror = function() {
100
+ self._connecting = false;
101
+ reject(new Error('Failed to load Socket.IO client'));
102
+ };
103
+ document.head.appendChild(cdnScript);
104
+ };
105
+ document.head.appendChild(script);
106
+ } else {
107
+ self._initSocket(socketUrl, token, resolve, reject);
108
+ }
109
+ });
110
+
111
+ return self._connectPromise;
112
+ },
113
+
114
+ // Event handler registrations
115
+ onJoined: function(callback) { this._eventHandlers.joined = callback; },
116
+ onPlayerJoined: function(callback) { this._eventHandlers.playerJoined = callback; },
117
+ onPlayerLeft: function(callback) { this._eventHandlers.playerLeft = callback; },
118
+ onStateUpdate: function(callback) { this._eventHandlers.stateUpdate = callback; },
119
+ onSync: function(callback) { this._eventHandlers.sync = callback; },
120
+ onAction: function(callback) { this._eventHandlers.action = callback; },
121
+ onRealtime: function(callback) { this._eventHandlers.realtime = callback; },
122
+ onGameFinished: function(callback) { this._eventHandlers.finished = callback; },
123
+ onGameRestarted: function(callback) { this._eventHandlers.restarted = callback; },
124
+ onError: function(callback) { this._eventHandlers.error = callback; },
125
+ onRematchRequest: function(callback) { this._eventHandlers.rematchRequest = callback; },
126
+ onDisconnect: function(callback) { this._eventHandlers.disconnect = callback; },
127
+ onReconnect: function(callback) { this._eventHandlers.reconnect = callback; },
128
+ onConnectionError: function(callback) { this._eventHandlers.connectionError = callback; },
129
+
130
+ /**
131
+ * Register a generic event handler
132
+ * @param {string} event - Event name
133
+ * @param {function} callback - Handler function
134
+ */
135
+ on: function(event, callback) {
136
+ if (this.socket) {
137
+ this.socket.on(event, callback);
138
+ }
139
+ }
140
+ };
141
+
142
+ // Apply sub-modules
143
+ applyGameDirect(game, Usion);
144
+ applyGameSocket(game, Usion);
145
+ applyGameProxy(game, Usion);
146
+ applyGameMethods(game, Usion);
147
+
148
+ return game;
149
+ }