Loading...
Loading...
Integrates the MSN JavaScript SDK (`window.$msstart`) into JavaScript games running on Microsoft Start. Handles SDK initialization, player authentication, cloud save key/value storage, display banner and interstitial/rewarded ad lifecycles, native sharing, leaderboard score submission, and in-app purchases. Use when the user mentions MSN SDK setup, MSN ads integration, game monetization with MSN, adding MSN features to a JavaScript game, `$msstart`, `msstart` script, MSN cloud save, or MSN IAP.
npx skill4agent add hungify/playables-skills msn-js-sdkwindow.$msstartmsstartwindow.$msstart.<method>Async(...)msstartwindow.$msstartuserAccountType === 'personal'visibilitychangeblurfocusDate.now()<script src='https://assets.msn.com/staticsb/statics/latest/msstart-games-sdk/msstart-v1.0.0-rc.21.min.js'>window.$msstart$msstartmsstart = window.$msstartgetSignedInUserAsync()isPlayerAuthorized = trueplayerIdisPlayerAuthorizeduserAccountType === 'personal'getDataAsync<script src="https://assets.msn.com/staticsb/statics/latest/msstart-games-sdk/msstart-v1.0.0-rc.21.min.js"></script>document.createElement('script')onloadonerrorwindow.$msstartsetTimeoutconst SDK_URL =
'https://assets.msn.com/staticsb/statics/latest/msstart-games-sdk/msstart-v1.0.0-rc.21.min.js';
const options = {
gameId: 'YOUR_MSN_GAME_ID',
payments: [
// { id: 'gem_pack_small', msn: { id: 'msstore-product-id' } }
]
};
let msstart = null;
let isInitialized = false;
let isPlayerAuthorized = false;
let playerId = null;
let playerName = null;
let playerExtra = null;
let isPaymentsSupported = false;
function updatePlayerInfo(data) {
if (data) {
isPlayerAuthorized = true;
playerId = data.playerId;
playerName = data.playerDisplayName;
playerExtra = data;
isPaymentsSupported = data.userAccountType.toLowerCase() === 'personal';
} else {
isPlayerAuthorized = false;
playerId = null;
playerName = null;
playerExtra = null;
isPaymentsSupported = false;
}
}
function initialize() {
if (isInitialized) return Promise.resolve();
return addJavaScript(SDK_URL)
.then(() => waitFor('$msstart'))
.then(() => {
msstart = window.$msstart;
return msstart
.getSignedInUserAsync()
.then((data) => updatePlayerInfo(data))
.catch(() => updatePlayerInfo(null));
})
.then(() => {
isInitialized = true;
});
}function authorizePlayer() {
if (isPlayerAuthorized) return Promise.resolve();
return msstart
.signInAsync()
.then((data) => updatePlayerInfo(data))
.catch((error) => {
updatePlayerInfo(null);
throw error;
});
}playerIdplayerDisplayNameuserAccountType'personal'$msstart.cloudSavegameIdnulllocalStoragelet cachedData = null;
function isCloudSaveAvailable() {
return isPlayerAuthorized;
}
function getStorageData(key) {
if (!isPlayerAuthorized) return Promise.reject();
const loadCache = cachedData
? Promise.resolve(cachedData)
: msstart.cloudSave.getDataAsync({ gameId: options.gameId }).then((data) => {
cachedData = data ?? {};
return cachedData;
});
return loadCache.then((data) => (Array.isArray(key) ? key.map((k) => data[k]) : data[key]));
}
function setStorageData(key, value) {
if (!isPlayerAuthorized) return Promise.reject();
const data = cachedData ? { ...cachedData } : {};
if (Array.isArray(key)) {
key.forEach((k, i) => {
data[k] = value[i];
});
} else {
data[key] = value;
}
return msstart.cloudSave.saveDataAsync({ data, gameId: options.gameId }).then(() => {
cachedData = data;
});
}
function deleteStorageData(key) {
if (!isPlayerAuthorized) return Promise.reject();
const payload = {};
const keys = Array.isArray(key) ? key : [key];
keys.forEach((k) => {
payload[k] = null;
if (cachedData) delete cachedData[k];
});
return msstart.cloudSave.saveDataAsync({ data: payload, gameId: options.gameId });
}'position:WxH'showDisplayAdsAsynchideDisplayAdsAsync()const MSN_SIZES_BY_POSITION = {
top: [
[728, 90],
[970, 250],
[320, 50]
],
bottom: [[320, 50]],
left: [
[300, 250],
[300, 600],
[320, 50],
[160, 600]
],
right: [
[300, 250],
[300, 600],
[320, 50],
[160, 600]
],
topleft: [[300, 250]],
topright: [[300, 250]],
bottomleft: [[300, 250]],
bottomright: [[300, 250]]
};msstart.showDisplayAdsAsync(['top:728x90']);
msstart.showDisplayAdsAsync(['bottom:320x50']);
msstart.hideDisplayAdsAsync();MSN_SIZES_BY_POSITION[position]s[0] <= width && s[1] <= height'position:WxH'loadAdsAsync(isRewarded)showAdsAsync(instanceId)showAdsCompletedAsyncfalsetrueshowAdsCompletedAsyncfunction showInterstitial({ onOpened, onClosed, onFailed } = {}) {
return msstart
.loadAdsAsync(false)
.then((adInstance) => msstart.showAdsAsync(adInstance.instanceId))
.then((adInstance) => {
if (onOpened) onOpened();
return adInstance.showAdsCompletedAsync;
})
.then(() => {
if (onClosed) onClosed();
})
.catch((error) => {
if (onFailed) onFailed(error);
});
}
function showRewarded({ onOpened, onRewarded, onClosed, onFailed } = {}) {
return msstart
.loadAdsAsync(true)
.then((adInstance) => msstart.showAdsAsync(adInstance.instanceId))
.then((adInstance) => {
if (onOpened) onOpened();
return adInstance.showAdsCompletedAsync;
})
.then(() => {
if (onRewarded) onRewarded();
if (onClosed) onClosed();
})
.catch((error) => {
if (onFailed) onFailed(error);
});
}'loading' | 'opened' | 'closed' | 'failed''rewarded'function share(shareOptions) {
// shareOptions: typically { title, text, url } — see msstart docs
return msstart.shareAsync(shareOptions);
}submitGameResultsAsync(score)function leaderboardsSetScore(score) {
return msstart.submitGameResultsAsync(score);
}$msstart.iapuserAccountType === 'personal'isPaymentsSupported{ code: 'IAP_*_FAILURE', description }codefunction paymentsGetCatalog() {
if (!options.payments) return Promise.reject();
return msstart.iap.getAllAddOnsAsync({ productId: options.gameId }).then((msnProducts) => {
if (msnProducts.code === 'IAP_GET_ALL_ADD_ONS_FAILURE') {
throw new Error(msnProducts.description);
}
return options.payments.map((product) => {
const platformId = product.msn?.id ?? product.id;
const p = msnProducts.find((m) => m.productId === platformId);
return {
id: product.id,
title: p.title,
description: p.description,
publisherName: p.publisherName,
inAppOfferToken: p.inAppOfferToken,
isConsumable: p.isConsumable,
price: `${p.price.listPrice} ${p.price.currencyCode}`,
priceCurrencyCode: p.price.currencyCode,
priceValue: p.price.listPrice
};
});
});
}{ receipt, receiptSignature }{ code: 'IAP_PURCHASE_FAILURE', description }const purchases = [];
async function paymentsPurchase(id) {
const product = options.payments.find((p) => p.id === id);
if (!product) return Promise.reject();
if (!isPlayerAuthorized) await authorizePlayer();
const platformProductId = product.msn?.id ?? product.id;
const purchase = await msstart.iap.purchaseAsync({ productId: platformProductId });
if (purchase.code === 'IAP_PURCHASE_FAILURE') throw new Error(purchase.description);
const merged = { id, ...purchase.receipt, receiptSignature: purchase.receiptSignature };
purchases.push(merged);
return merged;
}productId{ consumptionReceipt, consumptionSignature }{ code: 'IAP_CONSUME_FAILURE', description }async function paymentsConsumePurchase(id) {
const idx = purchases.findIndex((p) => p.id === id);
if (idx < 0) return Promise.reject();
if (!isPlayerAuthorized) await authorizePlayer();
const response = await msstart.iap.consumeAsync({ productId: purchases[idx].productId });
if (response.code === 'IAP_CONSUME_FAILURE') throw new Error(response.description);
purchases.splice(idx, 1);
const result = {
id,
...response.consumptionReceipt,
consumptionSignature: response.consumptionSignature
};
delete result.productId;
return result;
}IAP_GET_ALL_PURCHASES_FAILUREasync function paymentsGetPurchases() {
if (!isPlayerAuthorized) await authorizePlayer();
try {
const response = await msstart.iap.getAllPurchasesAsync({ productId: options.gameId });
purchases.length = 0;
response.receipts.forEach((purchase) => {
const product = options.payments.find(
(p) => (p.msn?.id ?? p.id) === purchase.productId
);
purchases.push({
id: product ? product.id : purchase.productId,
...purchase,
receiptSignature: response.receiptSignature
});
});
return purchases.slice();
} catch (error) {
if (error?.code === 'IAP_GET_ALL_PURCHASES_FAILURE') return [];
throw error;
}
}