@usions/sdk 2.16.0 → 2.18.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/package.json +1 -1
- package/src/browser.js +183 -2
- package/src/modules/core.js +12 -1
- package/src/modules/game-methods.js +11 -0
- package/src/modules/index.js +3 -0
- package/src/modules/permissions.js +157 -0
- package/types/index.d.ts +40 -2
package/package.json
CHANGED
package/src/browser.js
CHANGED
|
@@ -69,7 +69,7 @@ var Usion = (function () {
|
|
|
69
69
|
* Core Usion object with init, _post, _request
|
|
70
70
|
*/
|
|
71
71
|
const core = {
|
|
72
|
-
version: '2.
|
|
72
|
+
version: '2.18.0', // injected from package.json at build
|
|
73
73
|
config: {},
|
|
74
74
|
_initialized: false,
|
|
75
75
|
_initCallback: null,
|
|
@@ -263,14 +263,25 @@ var Usion = (function () {
|
|
|
263
263
|
* to a specific screen — e.g. when the user taps a notification sent via
|
|
264
264
|
* `Usion.notify.send({ path })`, the app reopens and `getLaunchParams().path`
|
|
265
265
|
* returns that same path so the app can route to it.
|
|
266
|
-
*
|
|
266
|
+
*
|
|
267
|
+
* `mode` is how the app was launched: `'single'` (opened from Explore / the
|
|
268
|
+
* Game hub, played solo) or `'multiplayer'` (opened from a game invite in a
|
|
269
|
+
* chat, played with another person). The host declares it authoritatively;
|
|
270
|
+
* branch on it to skip lobby/matchmaking UI in single-player or wire up
|
|
271
|
+
* netcode in multiplayer. When the host doesn't declare a mode (legacy hosts
|
|
272
|
+
* or a standalone URL), it falls back to "a room means multiplayer".
|
|
273
|
+
* @returns {{ path: string|null, ref: string|null, roomId: string|null, mode: 'single'|'multiplayer' }}
|
|
267
274
|
*/
|
|
268
275
|
getLaunchParams: function() {
|
|
269
276
|
var c = this.config || {};
|
|
277
|
+
var mode = (c.mode === 'single' || c.mode === 'multiplayer')
|
|
278
|
+
? c.mode
|
|
279
|
+
: (c.roomId ? 'multiplayer' : 'single');
|
|
270
280
|
return {
|
|
271
281
|
path: c.launchPath || null,
|
|
272
282
|
ref: c.ref || c.launchRef || null,
|
|
273
283
|
roomId: c.roomId || null,
|
|
284
|
+
mode: mode,
|
|
274
285
|
};
|
|
275
286
|
},
|
|
276
287
|
|
|
@@ -2507,6 +2518,17 @@ var Usion = (function () {
|
|
|
2507
2518
|
} catch (e) { /* non-fatal */ }
|
|
2508
2519
|
};
|
|
2509
2520
|
|
|
2521
|
+
/**
|
|
2522
|
+
* Whether this app was launched in multiplayer mode (opened from a game
|
|
2523
|
+
* invite in a chat) rather than single-player (opened from Explore / the
|
|
2524
|
+
* Game hub). Convenience wrapper over Usion.getLaunchParams().mode — use it
|
|
2525
|
+
* to branch setup, e.g. skip matchmaking and run solo when false.
|
|
2526
|
+
* @returns {boolean}
|
|
2527
|
+
*/
|
|
2528
|
+
game.isMultiplayer = function() {
|
|
2529
|
+
return Usion.getLaunchParams().mode === 'multiplayer';
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2510
2532
|
/**
|
|
2511
2533
|
* Get connection status
|
|
2512
2534
|
* @returns {boolean}
|
|
@@ -5090,6 +5112,163 @@ var Usion = (function () {
|
|
|
5090
5112
|
};
|
|
5091
5113
|
}
|
|
5092
5114
|
|
|
5115
|
+
/**
|
|
5116
|
+
* Usion SDK Permissions — a mini-app asks the user to allow capabilities.
|
|
5117
|
+
*
|
|
5118
|
+
* Works exactly like asking for money: the app requests, the HOST shows a modal,
|
|
5119
|
+
* the user **allows or cancels**, and the result flows back. The user can later
|
|
5120
|
+
* change any grant in the Usion app's settings for this app.
|
|
5121
|
+
*
|
|
5122
|
+
* const res = await Usion.permissions.request(['notifications']);
|
|
5123
|
+
* if (res.permissions.notifications) { ... } // granted
|
|
5124
|
+
*
|
|
5125
|
+
* const state = await Usion.permissions.query(['notifications']);
|
|
5126
|
+
* const ok = await Usion.permissions.has('notifications');
|
|
5127
|
+
*
|
|
5128
|
+
* Permissions are enforced by the PLATFORM, not by this return value — e.g.
|
|
5129
|
+
* `Usion.notify.send` is dropped (`delivered: 'blocked'`) until the user grants
|
|
5130
|
+
* `notifications`. So the pattern is: request first, then notify.
|
|
5131
|
+
*
|
|
5132
|
+
* This is an EMBEDDED feature. Running standalone (outside the Usion host) there
|
|
5133
|
+
* is no modal — request()/query() resolve to a benign "not granted" default and
|
|
5134
|
+
* the user manages the grant inside the Usion app.
|
|
5135
|
+
*/
|
|
5136
|
+
|
|
5137
|
+
function createPermissionsModule(Usion) {
|
|
5138
|
+
function normalizeList(perms) {
|
|
5139
|
+
if (typeof perms === 'string') return perms ? [perms] : [];
|
|
5140
|
+
if (Array.isArray(perms)) {
|
|
5141
|
+
return perms.filter(function (p) { return typeof p === 'string' && p; });
|
|
5142
|
+
}
|
|
5143
|
+
return [];
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
function isEmbedded() {
|
|
5147
|
+
return typeof window !== 'undefined' &&
|
|
5148
|
+
(!!window.ReactNativeWebView || (!!window.parent && window.parent !== window));
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
function denyAll(list) {
|
|
5152
|
+
const out = {};
|
|
5153
|
+
list.forEach(function (p) { out[p] = false; });
|
|
5154
|
+
return out;
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
function summarize(list, map) {
|
|
5158
|
+
const perms = map || {};
|
|
5159
|
+
const granted = list.length > 0 && list.every(function (p) { return !!perms[p]; });
|
|
5160
|
+
return { granted: granted, permissions: perms };
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
const api = {
|
|
5164
|
+
// How long to wait for the host's PERMISSION_RESULT before falling back to a
|
|
5165
|
+
// state query — the user may sit on the modal. We NEVER reject: a dropped
|
|
5166
|
+
// result must not strand the caller (mirrors wallet.requestPayment recovery).
|
|
5167
|
+
// Overridable, mainly for tests.
|
|
5168
|
+
_requestTimeoutMs: 120000,
|
|
5169
|
+
|
|
5170
|
+
/**
|
|
5171
|
+
* Ask the user to allow one or more permissions. Shows the host modal
|
|
5172
|
+
* (unless every requested permission is already granted, in which case the
|
|
5173
|
+
* host resolves immediately).
|
|
5174
|
+
*
|
|
5175
|
+
* @param {string|string[]} permissions e.g. 'notifications' or ['notifications']
|
|
5176
|
+
* @param {{reason?: string}} [opts] optional one-line context shown in the modal
|
|
5177
|
+
* @returns {Promise<{granted: boolean, permissions: Object}>}
|
|
5178
|
+
* `granted` is true only if EVERY requested permission ended granted;
|
|
5179
|
+
* `permissions` maps each requested key to its resulting boolean state.
|
|
5180
|
+
*/
|
|
5181
|
+
request: function (permissions, opts) {
|
|
5182
|
+
const list = normalizeList(permissions);
|
|
5183
|
+
const reason = (opts && typeof opts.reason === 'string') ? opts.reason : undefined;
|
|
5184
|
+
|
|
5185
|
+
if (!isEmbedded()) {
|
|
5186
|
+
try { console.warn('[Usion] permissions.request needs the Usion app; resolving as not granted.'); } catch (e) { /* noop */ }
|
|
5187
|
+
return Promise.resolve(summarize(list, denyAll(list)));
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
return new Promise(function (resolve) {
|
|
5191
|
+
const requestId = getNextRequestId();
|
|
5192
|
+
let settled = false;
|
|
5193
|
+
let timer = null;
|
|
5194
|
+
|
|
5195
|
+
function cleanup() {
|
|
5196
|
+
if (timer) clearTimeout(timer);
|
|
5197
|
+
window.removeEventListener('message', handler);
|
|
5198
|
+
}
|
|
5199
|
+
|
|
5200
|
+
function finish(result) {
|
|
5201
|
+
if (settled) return;
|
|
5202
|
+
settled = true;
|
|
5203
|
+
cleanup();
|
|
5204
|
+
resolve(result);
|
|
5205
|
+
}
|
|
5206
|
+
|
|
5207
|
+
function handler(event) {
|
|
5208
|
+
// Only honor results from the trusted host shell. (A forged result can
|
|
5209
|
+
// only mislead this app's UI — the backend gate is the real guard.)
|
|
5210
|
+
if (!isTrustedMessageSource(event)) return;
|
|
5211
|
+
let response;
|
|
5212
|
+
try {
|
|
5213
|
+
response = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
|
5214
|
+
} catch (e) { return; }
|
|
5215
|
+
if (!response || response._requestId !== requestId) return;
|
|
5216
|
+
if (response.type === 'PERMISSION_RESULT') {
|
|
5217
|
+
finish(summarize(list, response.permissions));
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
window.addEventListener('message', handler);
|
|
5222
|
+
|
|
5223
|
+
// Host never answered (lost message / older host shell): fall back to the
|
|
5224
|
+
// current persisted state so we still resolve with the truth.
|
|
5225
|
+
timer = setTimeout(function () {
|
|
5226
|
+
if (settled) return;
|
|
5227
|
+
api.query(list).then(function (map) {
|
|
5228
|
+
finish(summarize(list, map));
|
|
5229
|
+
}).catch(function () {
|
|
5230
|
+
finish(summarize(list, denyAll(list)));
|
|
5231
|
+
});
|
|
5232
|
+
}, api._requestTimeoutMs || 120000);
|
|
5233
|
+
|
|
5234
|
+
Usion._post({
|
|
5235
|
+
type: 'PERMISSION_REQUEST',
|
|
5236
|
+
_requestId: requestId,
|
|
5237
|
+
permissions: list,
|
|
5238
|
+
reason: reason,
|
|
5239
|
+
});
|
|
5240
|
+
});
|
|
5241
|
+
},
|
|
5242
|
+
|
|
5243
|
+
/**
|
|
5244
|
+
* Read current permission state WITHOUT prompting the user.
|
|
5245
|
+
* @param {string|string[]} [permissions]
|
|
5246
|
+
* @returns {Promise<Object>} map of permission key -> boolean (granted)
|
|
5247
|
+
*/
|
|
5248
|
+
query: function (permissions) {
|
|
5249
|
+
const list = normalizeList(permissions);
|
|
5250
|
+
if (!isEmbedded()) {
|
|
5251
|
+
return Promise.resolve(denyAll(list));
|
|
5252
|
+
}
|
|
5253
|
+
return Usion._request('PERMISSION_QUERY', { permissions: list }, 8000)
|
|
5254
|
+
.then(function (res) { return (res && res.permissions) || {}; });
|
|
5255
|
+
},
|
|
5256
|
+
|
|
5257
|
+
/**
|
|
5258
|
+
* Convenience: is a single permission currently granted?
|
|
5259
|
+
* @param {string} permission
|
|
5260
|
+
* @returns {Promise<boolean>}
|
|
5261
|
+
*/
|
|
5262
|
+
has: function (permission) {
|
|
5263
|
+
return api.query([permission]).then(function (map) {
|
|
5264
|
+
return !!(map && map[permission]);
|
|
5265
|
+
});
|
|
5266
|
+
},
|
|
5267
|
+
};
|
|
5268
|
+
|
|
5269
|
+
return api;
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5093
5272
|
/**
|
|
5094
5273
|
* Usion SDK — unified backend channel.
|
|
5095
5274
|
*
|
|
@@ -5228,6 +5407,8 @@ var Usion = (function () {
|
|
|
5228
5407
|
Usion.cloud = createCloudModule(Usion);
|
|
5229
5408
|
Usion.matchmaking = createMatchmakingModule(Usion);
|
|
5230
5409
|
Usion.notify = createNotifyModule(Usion);
|
|
5410
|
+
// Permission requests (host-mediated modal; embedded feature).
|
|
5411
|
+
Usion.permissions = createPermissionsModule(Usion);
|
|
5231
5412
|
|
|
5232
5413
|
// Netcode toolkit (transport-agnostic, zero-dependency).
|
|
5233
5414
|
Usion.netcode = netcode;
|
package/src/modules/core.js
CHANGED
|
@@ -260,14 +260,25 @@ export const core = {
|
|
|
260
260
|
* to a specific screen — e.g. when the user taps a notification sent via
|
|
261
261
|
* `Usion.notify.send({ path })`, the app reopens and `getLaunchParams().path`
|
|
262
262
|
* returns that same path so the app can route to it.
|
|
263
|
-
*
|
|
263
|
+
*
|
|
264
|
+
* `mode` is how the app was launched: `'single'` (opened from Explore / the
|
|
265
|
+
* Game hub, played solo) or `'multiplayer'` (opened from a game invite in a
|
|
266
|
+
* chat, played with another person). The host declares it authoritatively;
|
|
267
|
+
* branch on it to skip lobby/matchmaking UI in single-player or wire up
|
|
268
|
+
* netcode in multiplayer. When the host doesn't declare a mode (legacy hosts
|
|
269
|
+
* or a standalone URL), it falls back to "a room means multiplayer".
|
|
270
|
+
* @returns {{ path: string|null, ref: string|null, roomId: string|null, mode: 'single'|'multiplayer' }}
|
|
264
271
|
*/
|
|
265
272
|
getLaunchParams: function() {
|
|
266
273
|
var c = this.config || {};
|
|
274
|
+
var mode = (c.mode === 'single' || c.mode === 'multiplayer')
|
|
275
|
+
? c.mode
|
|
276
|
+
: (c.roomId ? 'multiplayer' : 'single');
|
|
267
277
|
return {
|
|
268
278
|
path: c.launchPath || null,
|
|
269
279
|
ref: c.ref || c.launchRef || null,
|
|
270
280
|
roomId: c.roomId || null,
|
|
281
|
+
mode: mode,
|
|
271
282
|
};
|
|
272
283
|
},
|
|
273
284
|
|
|
@@ -567,6 +567,17 @@ export function applyGameMethods(game, Usion) {
|
|
|
567
567
|
} catch (e) { /* non-fatal */ }
|
|
568
568
|
};
|
|
569
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Whether this app was launched in multiplayer mode (opened from a game
|
|
572
|
+
* invite in a chat) rather than single-player (opened from Explore / the
|
|
573
|
+
* Game hub). Convenience wrapper over Usion.getLaunchParams().mode — use it
|
|
574
|
+
* to branch setup, e.g. skip matchmaking and run solo when false.
|
|
575
|
+
* @returns {boolean}
|
|
576
|
+
*/
|
|
577
|
+
game.isMultiplayer = function() {
|
|
578
|
+
return Usion.getLaunchParams().mode === 'multiplayer';
|
|
579
|
+
};
|
|
580
|
+
|
|
570
581
|
/**
|
|
571
582
|
* Get connection status
|
|
572
583
|
* @returns {boolean}
|
package/src/modules/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import { createLeaderboardModule } from './leaderboard.js';
|
|
|
30
30
|
import { createCloudModule } from './cloud.js';
|
|
31
31
|
import { createMatchmakingModule } from './matchmaking.js';
|
|
32
32
|
import { createNotifyModule } from './notify.js';
|
|
33
|
+
import { createPermissionsModule } from './permissions.js';
|
|
33
34
|
import { applyBackendChannel } from './backend-channel.js';
|
|
34
35
|
import { netcode } from './netcode/index.js';
|
|
35
36
|
import { UsionError, ERROR_CODES } from './errors.js';
|
|
@@ -56,6 +57,8 @@ Usion.leaderboard = createLeaderboardModule(Usion);
|
|
|
56
57
|
Usion.cloud = createCloudModule(Usion);
|
|
57
58
|
Usion.matchmaking = createMatchmakingModule(Usion);
|
|
58
59
|
Usion.notify = createNotifyModule(Usion);
|
|
60
|
+
// Permission requests (host-mediated modal; embedded feature).
|
|
61
|
+
Usion.permissions = createPermissionsModule(Usion);
|
|
59
62
|
|
|
60
63
|
// Netcode toolkit (transport-agnostic, zero-dependency).
|
|
61
64
|
Usion.netcode = netcode;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usion SDK Permissions — a mini-app asks the user to allow capabilities.
|
|
3
|
+
*
|
|
4
|
+
* Works exactly like asking for money: the app requests, the HOST shows a modal,
|
|
5
|
+
* the user **allows or cancels**, and the result flows back. The user can later
|
|
6
|
+
* change any grant in the Usion app's settings for this app.
|
|
7
|
+
*
|
|
8
|
+
* const res = await Usion.permissions.request(['notifications']);
|
|
9
|
+
* if (res.permissions.notifications) { ... } // granted
|
|
10
|
+
*
|
|
11
|
+
* const state = await Usion.permissions.query(['notifications']);
|
|
12
|
+
* const ok = await Usion.permissions.has('notifications');
|
|
13
|
+
*
|
|
14
|
+
* Permissions are enforced by the PLATFORM, not by this return value — e.g.
|
|
15
|
+
* `Usion.notify.send` is dropped (`delivered: 'blocked'`) until the user grants
|
|
16
|
+
* `notifications`. So the pattern is: request first, then notify.
|
|
17
|
+
*
|
|
18
|
+
* This is an EMBEDDED feature. Running standalone (outside the Usion host) there
|
|
19
|
+
* is no modal — request()/query() resolve to a benign "not granted" default and
|
|
20
|
+
* the user manages the grant inside the Usion app.
|
|
21
|
+
*/
|
|
22
|
+
import { getNextRequestId, isTrustedMessageSource } from './core.js';
|
|
23
|
+
|
|
24
|
+
export function createPermissionsModule(Usion) {
|
|
25
|
+
function normalizeList(perms) {
|
|
26
|
+
if (typeof perms === 'string') return perms ? [perms] : [];
|
|
27
|
+
if (Array.isArray(perms)) {
|
|
28
|
+
return perms.filter(function (p) { return typeof p === 'string' && p; });
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isEmbedded() {
|
|
34
|
+
return typeof window !== 'undefined' &&
|
|
35
|
+
(!!window.ReactNativeWebView || (!!window.parent && window.parent !== window));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function denyAll(list) {
|
|
39
|
+
const out = {};
|
|
40
|
+
list.forEach(function (p) { out[p] = false; });
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function summarize(list, map) {
|
|
45
|
+
const perms = map || {};
|
|
46
|
+
const granted = list.length > 0 && list.every(function (p) { return !!perms[p]; });
|
|
47
|
+
return { granted: granted, permissions: perms };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const api = {
|
|
51
|
+
// How long to wait for the host's PERMISSION_RESULT before falling back to a
|
|
52
|
+
// state query — the user may sit on the modal. We NEVER reject: a dropped
|
|
53
|
+
// result must not strand the caller (mirrors wallet.requestPayment recovery).
|
|
54
|
+
// Overridable, mainly for tests.
|
|
55
|
+
_requestTimeoutMs: 120000,
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Ask the user to allow one or more permissions. Shows the host modal
|
|
59
|
+
* (unless every requested permission is already granted, in which case the
|
|
60
|
+
* host resolves immediately).
|
|
61
|
+
*
|
|
62
|
+
* @param {string|string[]} permissions e.g. 'notifications' or ['notifications']
|
|
63
|
+
* @param {{reason?: string}} [opts] optional one-line context shown in the modal
|
|
64
|
+
* @returns {Promise<{granted: boolean, permissions: Object}>}
|
|
65
|
+
* `granted` is true only if EVERY requested permission ended granted;
|
|
66
|
+
* `permissions` maps each requested key to its resulting boolean state.
|
|
67
|
+
*/
|
|
68
|
+
request: function (permissions, opts) {
|
|
69
|
+
const list = normalizeList(permissions);
|
|
70
|
+
const reason = (opts && typeof opts.reason === 'string') ? opts.reason : undefined;
|
|
71
|
+
|
|
72
|
+
if (!isEmbedded()) {
|
|
73
|
+
try { console.warn('[Usion] permissions.request needs the Usion app; resolving as not granted.'); } catch (e) { /* noop */ }
|
|
74
|
+
return Promise.resolve(summarize(list, denyAll(list)));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return new Promise(function (resolve) {
|
|
78
|
+
const requestId = getNextRequestId();
|
|
79
|
+
let settled = false;
|
|
80
|
+
let timer = null;
|
|
81
|
+
|
|
82
|
+
function cleanup() {
|
|
83
|
+
if (timer) clearTimeout(timer);
|
|
84
|
+
window.removeEventListener('message', handler);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function finish(result) {
|
|
88
|
+
if (settled) return;
|
|
89
|
+
settled = true;
|
|
90
|
+
cleanup();
|
|
91
|
+
resolve(result);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handler(event) {
|
|
95
|
+
// Only honor results from the trusted host shell. (A forged result can
|
|
96
|
+
// only mislead this app's UI — the backend gate is the real guard.)
|
|
97
|
+
if (!isTrustedMessageSource(event)) return;
|
|
98
|
+
let response;
|
|
99
|
+
try {
|
|
100
|
+
response = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
|
101
|
+
} catch (e) { return; }
|
|
102
|
+
if (!response || response._requestId !== requestId) return;
|
|
103
|
+
if (response.type === 'PERMISSION_RESULT') {
|
|
104
|
+
finish(summarize(list, response.permissions));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
window.addEventListener('message', handler);
|
|
109
|
+
|
|
110
|
+
// Host never answered (lost message / older host shell): fall back to the
|
|
111
|
+
// current persisted state so we still resolve with the truth.
|
|
112
|
+
timer = setTimeout(function () {
|
|
113
|
+
if (settled) return;
|
|
114
|
+
api.query(list).then(function (map) {
|
|
115
|
+
finish(summarize(list, map));
|
|
116
|
+
}).catch(function () {
|
|
117
|
+
finish(summarize(list, denyAll(list)));
|
|
118
|
+
});
|
|
119
|
+
}, api._requestTimeoutMs || 120000);
|
|
120
|
+
|
|
121
|
+
Usion._post({
|
|
122
|
+
type: 'PERMISSION_REQUEST',
|
|
123
|
+
_requestId: requestId,
|
|
124
|
+
permissions: list,
|
|
125
|
+
reason: reason,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Read current permission state WITHOUT prompting the user.
|
|
132
|
+
* @param {string|string[]} [permissions]
|
|
133
|
+
* @returns {Promise<Object>} map of permission key -> boolean (granted)
|
|
134
|
+
*/
|
|
135
|
+
query: function (permissions) {
|
|
136
|
+
const list = normalizeList(permissions);
|
|
137
|
+
if (!isEmbedded()) {
|
|
138
|
+
return Promise.resolve(denyAll(list));
|
|
139
|
+
}
|
|
140
|
+
return Usion._request('PERMISSION_QUERY', { permissions: list }, 8000)
|
|
141
|
+
.then(function (res) { return (res && res.permissions) || {}; });
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Convenience: is a single permission currently granted?
|
|
146
|
+
* @param {string} permission
|
|
147
|
+
* @returns {Promise<boolean>}
|
|
148
|
+
*/
|
|
149
|
+
has: function (permission) {
|
|
150
|
+
return api.query([permission]).then(function (map) {
|
|
151
|
+
return !!(map && map[permission]);
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return api;
|
|
157
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface UsionConfig {
|
|
|
31
31
|
launchPath?: string;
|
|
32
32
|
/** Referral code the app was opened with, if any. */
|
|
33
33
|
ref?: string;
|
|
34
|
+
/**
|
|
35
|
+
* How the app was launched: `'single'` (from Explore / the Game hub, solo)
|
|
36
|
+
* or `'multiplayer'` (from a game invite in a chat). Read via
|
|
37
|
+
* `Usion.getLaunchParams().mode`. (SDK ≥ 2.18)
|
|
38
|
+
*/
|
|
39
|
+
mode?: 'single' | 'multiplayer';
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
// ─── Payment ─────────────────────────────────────────────────────
|
|
@@ -256,6 +262,12 @@ export interface GameModule {
|
|
|
256
262
|
connectDirect(config?: DirectConnectionConfig): Promise<void>;
|
|
257
263
|
disconnect(): void;
|
|
258
264
|
isConnected(): boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Whether the app was launched multiplayer (from a chat game invite) vs
|
|
267
|
+
* single-player (from Explore / the Game hub). Wrapper over
|
|
268
|
+
* `Usion.getLaunchParams().mode === 'multiplayer'`. (SDK ≥ 2.18)
|
|
269
|
+
*/
|
|
270
|
+
isMultiplayer(): boolean;
|
|
259
271
|
|
|
260
272
|
// Room & actions
|
|
261
273
|
//
|
|
@@ -774,9 +786,11 @@ export interface UsionSDK {
|
|
|
774
786
|
/**
|
|
775
787
|
* Launch parameters the host opened this app with. `path` is the deep-link
|
|
776
788
|
* target (e.g. from a tapped `Usion.notify` notification); route to it after
|
|
777
|
-
* init so the user lands on the right screen.
|
|
789
|
+
* init so the user lands on the right screen. `mode` is `'single'` (opened
|
|
790
|
+
* from Explore / the Game hub, solo) or `'multiplayer'` (opened from a game
|
|
791
|
+
* invite in a chat) — branch game setup on it. (mode: SDK ≥ 2.18)
|
|
778
792
|
*/
|
|
779
|
-
getLaunchParams(): { path: string | null; ref: string | null; roomId: string | null };
|
|
793
|
+
getLaunchParams(): { path: string | null; ref: string | null; roomId: string | null; mode: 'single' | 'multiplayer' };
|
|
780
794
|
|
|
781
795
|
// Payments
|
|
782
796
|
requestPayment(amount: number, reason: string, data?: PaymentOptions): Promise<PaymentResponse>;
|
|
@@ -857,6 +871,7 @@ export interface UsionSDK {
|
|
|
857
871
|
matchmaking: MatchmakingModule;
|
|
858
872
|
cloud: CloudModule;
|
|
859
873
|
notify: NotifyModule;
|
|
874
|
+
permissions: PermissionsModule;
|
|
860
875
|
netcode: NetcodeModule;
|
|
861
876
|
}
|
|
862
877
|
|
|
@@ -874,6 +889,29 @@ export interface NotifyModule {
|
|
|
874
889
|
isMuted(opts?: { serviceId?: string }): Promise<boolean>;
|
|
875
890
|
}
|
|
876
891
|
|
|
892
|
+
/** Result of a permission request: per-key state + whether ALL were granted. */
|
|
893
|
+
export interface PermissionResult {
|
|
894
|
+
/** True only if every requested permission ended granted. */
|
|
895
|
+
granted: boolean;
|
|
896
|
+
/** Map of each requested permission key to its resulting state. */
|
|
897
|
+
permissions: Record<string, boolean>;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Ask the user to allow capabilities (the host shows a modal — like asking for
|
|
902
|
+
* money). The platform enforces grants: e.g. `notify.send` is dropped until
|
|
903
|
+
* `notifications` is granted. Embedded feature; standalone resolves "not
|
|
904
|
+
* granted". Permission keys today: `'notifications'`.
|
|
905
|
+
*/
|
|
906
|
+
export interface PermissionsModule {
|
|
907
|
+
/** Prompt the user to allow one or more permissions. */
|
|
908
|
+
request(permissions: string | string[], opts?: { reason?: string }): Promise<PermissionResult>;
|
|
909
|
+
/** Read current state without prompting; map of key -> granted. */
|
|
910
|
+
query(permissions?: string | string[]): Promise<Record<string, boolean>>;
|
|
911
|
+
/** Convenience: is a single permission currently granted? */
|
|
912
|
+
has(permission: string): Promise<boolean>;
|
|
913
|
+
}
|
|
914
|
+
|
|
877
915
|
/** Scoped server-persisted KV operations (per-user or shared). */
|
|
878
916
|
export interface CloudScope {
|
|
879
917
|
/** Get a value; resolves to null when the key doesn't exist. */
|