LayerG JavaScript Client Guide
This client library guide will show you how to use the core LayerG features in JavaScript by showing you how to develop the LayerG specific parts (without full game logic or UI) of an Among Us (external) inspired game called Sagi-shi (Japanese for “Imposter”).
Prerequisites
Before proceeding ensure that you have:
- Installed LayerG server
- Installed the LayerG JavaScript SDK
Full API documentation
For the full API documentation please visit the API docs.
Installation
The client is available on:
If using NPM or Yarn just add the dependency to your package.json
file:
yarn add "@layerglabs/LayerG-js"
yarn install
After installing the client import it into your project:
import {Client} from "@layerglabs/LayerG-js"
In your main JavaScript function create a client object.
Updates
New versions of the LayerG JavaScript Client and the corresponding improvements are documented in the Release Notes.
Asynchronous programming
Many methods of LayerG’s APIs available in the JavaScript SDK are asynchronous and non-blocking.
Sagi-shi calls async methods using the await
operator to not block the calling thread so that the game is responsive and efficient.
await client.authenticateDevice("<deviceId>");
Read about async
functions and the await
operator.
Handling exceptions
Network programming requires additional safeguarding against connection and payload issues.
API calls in Sagi-shi are surrounded with a try block and a catch clause to gracefully handle errors:
try {
await client.authenticateDevice("<deviceId>");
}
catch (err) {
console.log("Error authenticating device: %o:%o", err.statusCode, err.message);
}
For client request errors, the original error objects from the Fetch API are returned.
To capture the LayerG response associated with an error, invoke await error.json()
on the error object in the catch
block:
catch (err) {
console.log("LayerG Error:", await err.json());
}
Getting started
Learn how to get started using the LayerG Client and Socket objects to start building Sagi-shi and your own game.
LayerG Client
The LayerG Client connects to a LayerG Server and is the entry point to access LayerG features. It is recommended to have one client per server per game.
To create a client for Sagi-shi pass in your server connection details:
var client = new LayerGjs.Client("defaultkey", "127.0.0.1", 7350);
Configuring the Request Timeout Length
Each request to LayerG from the client must complete in a certain period of time before it is considered to have timed out. You can configure how long this period is (in milliseconds) by setting the timeout
value on the client:
client.timeout = 10000;
LayerG Socket
The LayerG Socket is used for gameplay and real-time latency-sensitive features such as chat, parties, matches and RPCs.
From the client create a socket:
const socket = client.createSocket();
var appearOnline = true;
await socket.connect(session, appearOnline);
Authentication
LayerG has many authentication methods and supports creating custom authentication on the server.
Sagi-shi will use device and Facebook authentication, linked to the same user account so that players can play from multiple devices.
Login screen and Authentication options
Device authentication
LayerG Device Authentication uses the physical device’s unique identifier to easily authenticate a user and create an account if one does not exist.
When using only device authentication, you don’t need a login UI as the player can automatically authenticate when the game launches.
Authentication is an example of a LayerG feature accessed from a LayerG Client instance.
// This import is only required with React Native
var deviceInfo = require('react-native-device-info');
var deviceId = null;
// If the user's device ID is already stored, grab that - alternatively get the System's unique device identifier.
try {
const value = await AsyncStorage.getItem('@MyApp:deviceKey');
if (value !== null){
deviceId = value
} else {
deviceId = deviceInfo.getUniqueID();
// Save the user's device ID so it can be retrieved during a later play session for re-authenticating.
AsyncStorage.setItem('@MyApp:deviceKey', deviceId).catch(function(error) {
console.log("An error occurred: %o", error);
});
}
} catch (error) {
console.log("An error occurred: %o", error);
}
// Authenticate with the LayerG server using Device Authentication.
var create = true;
const session = await client.authenticateDevice(deviceId, create, "mycustomusername");
console.info("Successfully authenticated:", session);
Facebook authentication
LayerG Facebook Authentication is an easy to use authentication method which lets you optionally import the player’s Facebook friends and add them to their LayerG Friends list.
const oauthToken = "<token>";
const importFriends = true;
try {
const session = await client.authenticateFacebook(oauthToken, true, "mycustomusername", importFriends);
console.log("Successfully authenticated:", session);
}
catch(err) {
console.log("Error authenticating with Facebook: %o", err.message);
}
Custom authentication
LayerG supports Custom Authentication methods to integrate with additional identity services.
See the Itch.io custom authentication recipe for an example.
Linking authentication
LayerG allows players to Link Authentication methods to their account once they have authenticated.
Linking Device ID authentication
// Acquiring the unique device ID has been shortened for brevity, see previous example.
var deviceId = "<uniqueDeviceId>";
// Link Device Authentication to existing player account.
try {
await client.linkDevice(session, deviceId);
console.log("Successfully linked Device ID authentication to existing player account");
}
catch(err) {
console.log("Error linking Device ID: %o", err.message);
}
Linking Facebook authentication
const oauthToken = "<token>";
const import = true;
try {
const session = await client.linkFacebook(session, oauthToken, true, import);
console.log("Successfully linked Facebook authentication to existing player account");
}
catch(err) {
console.log("Error authenticating with Facebook: %o", err.message);
}
Session variables
LayerG Session Variables can be stored when authenticating and will be available on the client and server as long as the session is active.
Sagi-shi uses session variables to implement analytics, referral and rewards programs and more.
Store session variables by passing them as an argument when authenticating:
const vars = {
deviceId = localStorage.getItem("deviceId"),
deviceOs = localStorage.getItem("deviceOs"),
inviteUserId = "<someUserId>",
// ...
}
const session = await client.authenticateDevice(deviceId, null, true, vars);
To access session variables on the Client use the vars
property on the session
object:
var deviceOs = session.vars["deviceOs"];
Session lifecycle
LayerG Sessions expire after a time set in your server configuration. Expiring inactive sessions is a good security practice.
LayerG provides ways to restore sessions, for example when Sagi-shi players re-launch the game, or refresh tokens to keep the session active while the game is being played.
Use the auth and refresh tokens on the session object to restore or refresh sessions.
Store the tokens for use later:
var authToken = session.token;
var refreshToken = session.refresh_token;
Restore a session without having to re-authenticate:
session = session.restore(authToken, refreshToken);
Check if a session has expired or is close to expiring and refresh it to keep it alive:
// Check whether a session has expired or is close to expiry.
if (session.isexpired || session.isexpired(Date.now + 1) {
try {
// Attempt to refresh the existing session.
session = await client.sessionRefresh(session);
} catch (error) {
// Couldn't refresh the session so reauthenticate.
session = await client.authenticateDevice(deviceId);
var refreshToken = session.refresh_token;
}
var authToken = session.token;
}
Automatic session refresh
The JavaScript client library includes a feature where sessions close to expiration are automatically refreshed.
This is enabled by default but can be configured when first creating the LayerG client using the following parameters:
autoRefreshSession
- Boolean value indicating if this feature is enabled,true
by defaultexpiredTimespanMs
- The time prior to session expiry when auto-refresh will occur, set to300000
(5 minutes) be default
Ending sessions
Logout and end the current session:
await client.sessionLogout(session);
User accounts
LayerG User Accounts store user information defined by LayerG and custom developer metadata.
Sagi-shi allows players to edit their accounts and stores metadata for things like game progression and in-game items.
Player profile
Get the user account
Many of LayerG’s features are accessible with an authenticated session, like fetching a user account.
Get a Sagi-shi player’s full user account with their basic user information and user id:
const account = await client.getAccount(session);
const user = account.user;
var username = user.username;
var avatarUrl = user.avatarUrl;
var userId = user.id;
Update the user account
LayerG provides easy methods to update server stored resources like user accounts.
Sagi-shi players need to be able to update their public profiles:
var newUsername = "NotTheImp0ster";
var newDisplayName = "Innocent Dave";
var newAvatarUrl = "https://example.com/imposter.png";
var newLangTag = "en";
var newLocation = "Edinburgh";
var newTimezone = "BST";
await client.updateAccount(session, newUsername, newDisplayName, newAvatarUrl, newLangTag, newLocation, newTimezone);
Getting users
In addition to getting the current authenticated player’s user account, LayerG has a convenient way to get a list of other players’ public profiles from their ids or usernames.
Sagi-shi uses this method to display player profiles when engaging with other LayerG features:
var users = await client.getUsers(session, ["<AnotherUserId>"]);
Storing metadata
LayerG User Metadata allows developers to extend user accounts with public user fields.
User metadata can only be updated on the server. See the updating user metadata recipe for an example.
Sagi-shi will use metadata to store what in-game items players have equipped:
Reading metadata
Get the updated account object and parse the JSON metadata:
// Get the updated account object.
var account = await client.getAccount(session);
// Parse the account user metadata.
var metadata = JSON.parse(account.user.metadata);
console.log("Title: %o", metadata.title);
console.log("Hat: %o", metadata.hat);
console.log("Skin: %o", metadata.skin);
Wallets
LayerG User Wallets can store multiple digital currencies as key/value pairs of strings/integers.
Players in Sagi-shi can unlock or purchase titles, skins and hats with a virtual in-game currency.
Accessing wallets
Parse the JSON wallet data from the user account:
var account = await client.getAccount(session);
var wallet = JSON.parse(account.wallet);
var keys = wallet.keys;
keys.forEach(function(currency) {
console.log("%o: %o", currency, wallet[currency].toString())
});
Updating wallets
Wallets can only be updated on the server. See the user account virtual wallet documentation for an example.
Validating in-app purchases
Sagi-shi players can purchase the virtual in-game currency through in-app purchases that are authorized and validated to be legitimate on the server.
See the In-app Purchase Validation documentation for examples.
Storage Engine
The LayerG Storage Engine is a distributed and scalable document-based storage solution for your game.
The Storage Engine gives you more control over how data can be accessed and structured in collections.
Collections are named, and store JSON data under a unique key and the user id.
By default, the player has full permission to create, read, update and delete their own storage objects.
Sagi-shi players can unlock or purchase many items, which are stored in the Storage Engine.
Player items
Reading storage objects
Create a new storage object id with the collection name, key and user id. Then read the storage objects and parse the JSON data:
var readObjectId = new storageObjectId {
collection = "Unlocks",
key = "Hats",
userId = session.user.id
};
var result = await client.readStorageObjects(session, readObjectId);
if (result.objects.any())
{
var storageObject = result.objects.first();
var unlockedHats = JSON.parse(storageObject.value);
console.log("Unlocked hats: %o", string.join(",", unlockedHats.Hats));
}
To read other players’ public storage objects use their UserId
instead. Remember that players can only read storage objects they own or that are public (PermissionRead
value of 2
).
Writing storage objects
LayerG allows developers to write to the Storage Engine from the client and server.
Consider what adverse effects a malicious user can have on your game and economy when deciding where to put your write logic, for example data that should only be written authoritatively (i.e. game unlocks or progress).
Sagi-shi allows players to favorite items for easier access in the UI and it is safe to write this data from the client.
Create a write storage object with the collection name, key and JSON encoded data. Finally, write the storage objects to the Storage Engine:
var favoriteHats = new {
hats = ["cowboy", "alien"]
};
var writeObject = new WriteStorageObject {
collection = "favorites",
key = "Hats",
value = JSON.stringify(favoriteHats),
permissionRead = 1, // Only the server and owner can read
permissionWrite = 1 // The server and owner can write
};
await client.writeStorageObjects(session, writeObject);
You can also pass multiple objects to the WriteStorageObjectsAsync
method:
var writeObjects = {
new WriteStorageObject {
//...
},
new WriteStorageObject
{
// ...
}
};
await client.writeStorageObjects(session, writeObjects);
Conditional writes
Storage Engine Conditional Writes ensure that write operations only happen if the object hasn’t changed since you accessed it.
This gives you protection from overwriting data, for example the Sagi-shi server could have updated an object since the player last accessed it.
To perform a conditional write, add a version to the write storage object with the most recent object version:
// Assuming we already have a storage object (storageObject)
var writeObject = new WriteStorageObject {
collection = storageObject.collection,
key = storageObject.key,
value = "<NewJSONValue>",
permissionWrite = 0,
permissionRead = 1,
version = storageObject.version
};
try {
await client.writeStorageObjects(session, writeObjects);
}
catch (error) {
console.log(error.message);
}
Listing storage objects
Instead of doing multiple read requests with separate keys you can list all the storage objects the player has access to in a collection.
Sagi-shi lists all the player’s unlocked or purchased titles, hats and skins:
var limit = 3;
var cursor = null;
var unlocksObjectList = await client.listStorageObjects(session, "Unlocks", limit, cursor);
unlocksObjectList.objects.forEach(function(unlockStorageObject) {
switch(unlockStorageObject.key) {
case "Titles":
var unlockedTitles = JSON.parse<TitlesStorageObject>(unlockStorageObject.value);
// Display the unlocked titles
break;
case "Hats":
var unlockedHats = JSON.parse<HatsStorageObject>(unlockStorageObject.value);
// Display the unlocked hats
break;
case "Skins":
var unlockedSkins = JSON.parse<SkinsStorageObject>(unlockStorageObject.value);
// Display the unlocked skins
break;
}
});
Paginating results
LayerG methods that list results return a cursor which can be passed to subsequent calls to LayerG to indicate where to start retrieving objects from in the collection.
For example:
- If the cursor has a value of 5, you will get results from the fifth object.
- If the cursor is
null
, you will get results from the first object.
objectList = await client.listStorageObjects(session, "<CollectionName>", limit, objectList.cursor);
Protecting storage operations on the server
LayerG Storage Engine operations can be protected on the server to protect data the player shouldn’t be able to modify (i.e. game unlocks or progress). See the writing to the Storage Engine authoritatively recipe.
Remote Procedure Calls
The LayerG Server allows developers to write custom logic and expose it to the client as RPCs.
Sagi-shi contains various logic that needs to be protected on the server, like checking if the player owns equipment before equipping it.
Creating server logic
See the handling player equipment authoritatively recipe for an example of creating a remote procedure to check if the player owns equipment before equipping it.
Client RPCs
LayerG Remote Procedures can be called from the client and take optional JSON payloads.
The Sagi-shi client makes an RPC to securely equip a hat:
try {
var payload = { "item": "cowboy"};
var response = await client.rpc(session, "EquipHat", payload);
console.log("New hat equipped successfully", response);
}
catch (error) {
console.log("Error: %o", error.message);
}
Socket RPCs
LayerG Remote Procedures can also be called from the socket when you need to interface with LayerG’s real-time functionality. These real-time features require a live socket (and corresponding session identifier). RPCs can be made on the socket carrying this same identifier.
var response = await socket.rpc("<rpcId>", "<payloadString>");