Docs
Direct Message Sharing
Enable users to share content directly to their friends via TikTok's direct messaging system. This guide covers the implementation of friend list sharing, open data domain communication, and social sharing capabilities within mini games.
The direct message sharing capability allows mini games to:
- Display a friend list interface for users to select recipients
- Share game content directly to specific friends via TikTok messages
- Store and retrieve user data in the cloud for leaderboards and game state
- Communicate between the main game domain and the open data domain
Prerequisites
Before implementing direct message sharing, ensure you have the following:
- A mini game registered on the Developer Portal
- Client version 40.3.0 or higher for sharing capabilities
- Understanding of the open data domain architecture
Version compatibility check
Use the canIUse API to verify that the user's client supports the sharing functionality:
// Check if shareAppMessage is supported
const isSupported = TTMinis.game.canIUse('shareAppMessage');
if (!isSupported) {
// Fallback behavior or prompt user to update
console.log('Please update TikTok to the latest version to use this feature.');
}Architecture overview
The direct message sharing system uses a dual-domain architecture:
- Main Game Domain: The primary game environment where your game logic runs
- Open Data Domain: An isolated environment for handling sensitive social data (friend lists, user information)
Communication between these domains happens through a secure message-passing system using a shared canvas.
Set up the open data domain
The open data domain is required to access social features like friend lists and direct messaging. This domain runs in an isolated iframe with limited capabilities for security purposes.
Project configuration
Add the open data context configuration to your project.config.json file:
{
"dev": {
"port": 9527
},
"openDataContext": "openDataContext",
"app_id": "your_client_key"
}The openDataContext field specifies the directory containing your open data domain code. The system will automatically load openDataContext/index.js as the entry point.
Native game engine integration
For games built with Cocos, Laya, Egret, or other native game engines, the open data domain can be configured to work alongside your main game. Refer to your game engine's documentation for specific integration steps.
Canvas configuration
The open data domain renders content on a shared canvas that overlays your main game. You must configure this canvas position and dimensions appropriately.
Set canvas position and dimensions
// Get the shared canvas from the game global context
let sharedCanvas = GameGlobal.tt.getSharedCanvas();
let sharedContext = sharedCanvas.getContext('2d');
// Set the canvas resolution (internal coordinate system)
sharedCanvas.width = 800;
sharedCanvas.height = 800;
// Set the display size and position using CSS-style properties
sharedCanvas.style.width = '80vw'; // 80% of viewport width
sharedCanvas.style.height = '40vh'; // 40% of viewport height
sharedCanvas.style.top = '20vh'; // Position from top
sharedCanvas.style.left = '10vw'; // Position from leftCanvas best practices
- Set the internal resolution (
width/height) high enough for crisp rendering on high-DPI displays - Use viewport-relative units (
vw,vh) for responsive positioning across different screen sizes - Ensure the canvas doesn't obscure critical game UI elements
Mini Game Domain APIs
These methods are called from your main game code to interact with the open data domain and social features.
getOpenDataContext
Retrieves a reference to the open data context for message passing.
const openDataContext = TTMinis.game.getOpenDataContext();
const { postMessage } = openDataContext;
// Send a message to the open data domain
postMessage({
type: 'show',
data: {}
});For native games, use the global tt object:
const { postMessage } = tt.getOpenDataContext();
postMessage({ type: 'show', data: {} });authorizeOpenContext
Requests authorization to access the user's friend list and social data. This must be called before accessing any social features.
TTMinis.game.authorizeOpenContext({
// When get_status_only is true, only checks status without showing UI
get_status_only: false,
success: (result) => {
const { code } = result;
// Use the code for authentication
console.log('Authorization code:', code);
},
fail: (error) => {
console.error('Authorization failed:', error);
},
complete: () => {
console.log('Authorization flow completed');
}
});Native implementation:
tt.authorizeOpenContext({
get_status_only: false,
success: (result) => {
const { code } = result;
},
fail: () => {},
complete: () => {}
});Parameters
Field | Type | Description | Required |
get_status_only | boolean | When | No |
success | function | Callback on successful authorization | No |
fail | function | Callback on authorization failure | No |
complete | function | Callback when authorization flow completes (success or failure) | No |
Success Callback Result
Field | Type | Description |
code | string | Authorization code for authenticating with the open data domain |
setUserCloudStorage
Stores user data in the cloud for persistence across sessions and for sharing with friends (e.g., high scores, game progress).
TTMinis.game.setUserCloudStorage({
data: [
{ key: 'highScore', value: '10000' },
{ key: 'level', value: '15' }
],
success: () => {
console.log('Data saved successfully');
},
fail: (error) => {
console.error('Failed to save data:', error);
}
});Native implementation:
tt.setUserCloudStorage({
data: [{ key: 'highScore', value: '10000' }],
success: () => {},
fail: () => {}
});Parameters
Field | Type | Description | Required |
data | Array<Object> | Array of key-value pairs to store | Yes |
data[].key | string | Key name (max 128 characters) | Yes |
data[].value | string | Value to store (max 2048 characters) | Yes |
success | function | Callback on successful save | No |
fail | function | Callback on save failure | No |
complete | function | Callback when operation completes | No |
removeUserCloudStorage
Removes specific keys from the user's cloud storage.
TTMinis.game.removeUserCloudStorage({
keyList: ['highScore', 'tempData'],
success: () => {
console.log('Data removed successfully');
},
fail: (error) => {
console.error('Failed to remove data:', error);
}
});Native implementation:
tt.removeUserCloudStorage({
keyList: ['highScore'],
success: () => {},
fail: () => {}
});Parameters
Field | Type | Description | Required |
keyList | Array<string> | Array of keys to remove | Yes |
success | function | Callback on successful removal | No |
fail | function | Callback on removal failure | No |
complete | function | Callback when operation completes | No |
Open Data Domain APIs
These methods are called from within the open data domain (openDataContext/index.js). The open data domain has limited access to system APIs and primarily handles rendering the friend list and processing social data.
onMessage
Listens for messages sent from the main game domain via postMessage.
tt.onMessage((data) => {
console.log('Received message from main domain:', data);
switch (data.command) {
case 'render':
// Render the friend list
renderFriendList();
break;
case 'hide':
// Clear the canvas
clearCanvas();
break;
default:
console.log('Unknown command:', data.command);
}
});Animation Frame Methods
The open data domain supports standard animation frame methods for smooth rendering:
// Request an animation frame
const frameId = tt.requestAnimationFrame(() => {
// Render logic here
drawFriendList();
});
// Cancel a scheduled animation frame
tt.cancelAnimationFrame(frameId);Timer Methods
Standard timer methods are available in the open data domain:
// Set a timeout
const timeoutId = tt.setTimeout(() => {
console.log('Timeout executed');
}, 1000);
// Clear a timeout
tt.clearTimeout(timeoutId);getSharedCanvas
Gets the shared canvas for rendering the open data domain content.
const canvas = tt.getSharedCanvas();
const ctx = canvas.getContext('2d');
// Set canvas dimensions
canvas.width = 400;
canvas.height = 200;
// Draw content
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);createImage
Creates an image object for loading and displaying images in the open data domain.
const image = tt.createImage();
image.src = 'https://example.com/image.png';
image.onload = () => {
console.log('Image loaded successfully');
// Draw image to canvas
ctx.drawImage(image, 0, 0);
};
image.onerror = (error) => {
console.error('Failed to load image:', error);
};getFriendCloudStorage
Retrieves cloud storage data for the user's friends. This is useful for building leaderboards.
GameGlobal.tt.getFriendCloudStorage({
keyList: ['highScore', 'level'],
success: (data) => {
console.log('Friend data retrieved:', data);
// Process friend data for leaderboard display
data.data.forEach(friend => {
console.log(`Friend: ${friend.nickname}, Score: ${friend.KVDataList[0].value}`);
});
},
fail: (error) => {
console.error('Failed to get friend data:', error);
},
complete: () => {
console.log('Friend data request completed');
}
});Parameters
Field | Type | Description | Required |
keyList | Array<string> | Array of keys to retrieve for each friend | Yes |
success | function | Callback on successful retrieval | No |
fail | function | Callback on retrieval failure | No |
complete | function | Callback when operation completes | No |
Success Callback Result
Field | Type | Description |
data | Array<Object> | Array of friend data objects |
data[].avatarUrl | string | Friend's avatar URL |
data[].nickname | string | Friend's nickname |
data[].openid | string | Friend's OpenID (unique identifier) |
data[].KVDataList | Array<Object> | Array of key-value pairs for the requested keys |
getUserCloudStorage
Retrieves the current user's cloud storage data within the open data domain.
GameGlobal.tt.getUserCloudStorage({
keyList: ['key_test_abc'],
success: (data) => {
console.log('User data retrieved:', data);
},
fail: (error) => {
console.error('Failed to get user data:', error);
},
complete: () => {
console.log('User data request completed');
}
});setUserCloudStorage (Open Data Domain)
Sets cloud storage data from within the open data domain.
GameGlobal.tt.setUserCloudStorage({
data: [
{ key: 'key_test_abc', value: 'value_test_abc' }
],
success: (data) => {
console.log('Data saved:', data);
},
fail: (error) => {
console.error('Failed to save:', error);
},
complete: () => {
console.log('Save operation completed');
}
});removeUserCloudStorage (Open Data Domain)
Removes cloud storage data from within the open data domain.
GameGlobal.tt.removeUserCloudStorage({
keyList: ['key_test_abc'],
success: (data) => {
console.log('Data removed:', data);
},
fail: (error) => {
console.error('Failed to remove:', error);
},
complete: () => {
console.log('Remove operation completed');
}
});Social sharing methods
shareToUser
Initiates sharing to a specific friend via direct message. This method can only be called from within the open data domain.
tt.shareToUser({
imageUrl: 'https://example.com/share-image.png',
openid: 'friend_openid_from_friend_list',
subtitle: 'Check out my new high score!',
templateType: 1,
title: 'Beat my score in Super Game!',
success: () => {
console.log('Share completed successfully');
},
fail: (error) => {
console.error('Share failed:', error);
}
});Parameters
Field | Type | Description | Required |
imageUrl | string | URL of the image to display in the share card | Yes |
openid | string | OpenID of the friend to share with (obtained from friend list) | Yes |
subtitle | string | Subtitle text displayed in the share card | No |
templateType | number | Visual template style: | No |
title | string | Main title displayed in the share card | No |
success | function | Callback on successful share | No |
fail | function | Callback on share failure | No |
Template types
Two visual templates are available for share cards:
- Template Type 1: CTA template, standard share card layout with title, subtitle, and image
- Template Type 2: Interactive template, alternative layout with different visual styling
Choose the template that best fits your game's branding and content.
shareAppMessage
Initiates sharing via direct message from the main game domain, opening the contact selection interface. This method can only be called from the main game domain.
Note: Requires client version 40.3.0 or higher.
tt.shareAppMessage({
imageUrl: 'https://example.com/share-image.png',
subtitle: 'Join me in this amazing game!',
templateType: 1,
title: 'Play Super Game with me!',
success: () => {
console.log('Share initiated successfully');
},
fail: (error) => {
console.error('Share failed:', error);
}
});Parameters
Field | Type | Description | Required |
imageUrl | string | URL of the image to display in the share card | Yes |
subtitle | string | Subtitle text displayed in the share card | No |
templateType | number | Visual template style: | No |
title | string | Main title displayed in the share card | No |
success | function | Callback on successful share | No |
fail | function | Callback on share failure | No |
shareToStory
Allows users to share content to their TikTok story. This method can only be called from the main game domain.
tt.shareToStory({
success: () => {
console.log('Shared to story successfully');
},
fail: (error) => {
console.error('Share to story failed:', error);
}
});Implementation example
Here's a complete example showing how to implement the friend list sharing flow:
Main Game Domain
class Game {
private openDataContext: any;
constructor() {
// Initialize open data context
this.openDataContext = TTMinis.game.getOpenDataContext();
// Check if sharing is supported
if (!TTMinis.game.canIUse('shareAppMessage')) {
console.warn('direct message sharing not supported on this client version');
return;
}
}
// Show friend list for sharing
showFriendList() {
// Request authorization first
TTMinis.game.authorizeOpenContext({
get_status_only: false,
success: (result) => {
// Send message to open data domain to render friend list
this.openDataContext.postMessage({
type: 'showFriendList',
data: {
authCode: result.code
}
});
},
fail: (error) => {
console.error('Authorization failed:', error);
}
});
}
// Hide friend list
hideFriendList() {
this.openDataContext.postMessage({
type: 'hide'
});
}
// Save high score
saveHighScore(score: number) {
TTMinis.game.setUserCloudStorage({
data: [
{ key: 'highScore', value: score.toString() }
],
success: () => {
console.log('High score saved');
}
});
}
// Share via direct message (main domain method)
shareToFriend() {
tt.shareAppMessage({
title: 'Beat my high score!',
subtitle: `I just scored ${this.currentScore} points!`,
imageUrl: 'https://your-cdn.com/share-card.png',
templateType: 1,
success: () => {
console.log('Share successful');
}
});
}
}Open Data Domain (openDataContext/index.js)
// Listen for messages from main domain
tt.onMessage((message) => {
switch (message.type) {
case 'showFriendList':
renderFriendList();
break;
case 'hide':
clearCanvas();
break;
}
});
// Get canvas context
const canvas = tt.getSharedCanvas();
const ctx = canvas.getContext('2d');
// Render friend list
function renderFriendList() {
// Get friend data with cloud storage
tt.getFriendCloudStorage({
keyList: ['highScore'],
success: (result) => {
const friends = result.data;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw friend list
friends.forEach((friend, index) => {
const y = index * 80 + 20;
// Draw avatar
const avatar = tt.createImage();
avatar.src = friend.avatarUrl;
avatar.onload = () => {
ctx.drawImage(avatar, 20, y, 60, 60);
};
// Draw name
ctx.fillStyle = '#333333';
ctx.font = '16px sans-serif';
ctx.fillText(friend.nickname, 100, y + 30);
// Draw high score
const score = friend.KVDataList[0]?.value || '0';
ctx.fillStyle = '#666666';
ctx.font = '14px sans-serif';
ctx.fillText(`Score: ${score}`, 100, y + 50);
});
}
});
}
// Clear canvas
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Handle share to specific user
function shareToFriend(openid) {
tt.shareToUser({
openid: openid,
title: 'Join me in this game!',
subtitle: 'Let\'s play together',
imageUrl: 'https://your-cdn.com/share-card.png',
templateType: 1,
success: () => {
console.log('Shared successfully');
}
});
}Best Practices
- Version Checking: Always use
canIUseto check if sharing features are available before attempting to use them. - Error Handling: Implement proper error handling for all sharing methods to gracefully handle network issues or user cancellations.
- Performance: The open data domain runs in a separate context. Minimize message passing between domains and batch operations when possible.
- User Experience: Show loading states while fetching friend data and provide clear feedback when sharing succeeds or fails.
- Data Privacy: Only request the minimum necessary permissions. Use
get_status_only: trueto check authorization status without prompting the user when appropriate. - Canvas Management: Ensure the shared canvas is properly sized and positioned to not interfere with game controls.
Troubleshooting
Issue | Possible Cause | Solution |
| Client version too old | Prompt user to update TikTok to version 40.3.0 or higher |
Friend list not displaying | Open data domain not configured | Check |
Canvas content not visible | Canvas positioning incorrect | Verify canvas style properties (width, height, top, left) are set correctly |
Authorization fails | User declined permission | Handle |
Cloud storage data not persisting | Key format incorrect | Ensure keys are strings and values are JSON-serializable |
Share card not displaying correctly | Image URL inaccessible | Verify image URL is publicly accessible and properly formatted |