跳到主要内容

隐私管理

1. 隐私权限检查​

接口原型​

gameHandle.setPrivacyChecker(checker)

参数说明​

参数数据类型必填属性说明
checkerObject是隐私权限检查器对象

2. 宿主隐私协议接入​

功能介绍​

为了规范开发者调用系统能力的行为,如相机、蓝牙、传感器等。宿主给第三方接入平台提供了权限检查的异步调用接口,第三方平台可以在接口中检查或引导用户授予相关权限。

接入说明​

1. 实现隐私检查器对象

隐私检查器需要实现 requirePrivacy 方法,其中 methodName 的可能值见下节 相关 API 部分。

const privacyChecker = {
/**
* Get system capability related permissions
* @param {string} methodName - Method name that needs permission check
* @param {Function} callback - Permission check result callback
*/
requirePrivacy: (methodName, callback) => {
// Implement your permission check logic here
// callback should be called with result object
}
};

2. 回调结果对象定义

callback 函数接收一个结果对象,包含以下字段:

{
passed: Boolean, // Whether permission check passed
errorNo: Number, // Error code (0 for success)
errorMessage: String // Error message
}

参数说明:

  • passed: 表示是否检查通过
  • errorNo: 错误码,通过时为 0
  • errorMessage: 错误消息,会返回给游戏中的 js 调用

3. 通过 RuntimeGameHandle 设置隐私检查器

const gameHandle = /* RuntimeGameHandle instance */;
gameHandle.setPrivacyChecker(privacyChecker);

3. 相关 API​

以下是需要隐私权限检查的系统能力:

信息类型methodName说明
麦克风startRecorderManagerRecorderManager.start
摄像头startCameraManagertj.createCamera
蓝牙openBluetoothAdapter, createBLEPeripheralServertj.openBluetoothAdapter, tj.createBLEPeripheralServer
陀螺仪传感器startGyroscopetj.startGyroscope
方向传感器startDeviceMotionListeningtj.startDeviceMotionListening
磁场传感器startCompasstj.startCompass
加速传感器startAccelerometertj.startAccelerometer

4. 示例​

基本示例​

const privacyChecker = {
requirePrivacy: (methodName, callback) => {
console.log(`Permission check requested for: ${methodName}`);

// Example: Always grant permission
callback({
passed: true,
errorNo: 0,
errorMessage: ''
});
}
};

gameHandle.setPrivacyChecker(privacyChecker);

随机结果示例​

一个返回随机结果的隐私检查器,用于测试:

class RandomPrivacyChecker {
requirePrivacy(methodName, callback) {
console.log(`Checking privacy for: ${methodName}`);

// Simulate async permission check
setTimeout(() => {
const result = this.getRandomResult();
console.log(`Privacy check result: ${result.passed ? 'GRANTED' : 'DENIED'}`);
callback(result);
}, 500);
}

getRandomResult() {
const random = Math.random();
if (random < 0.7) {
return {
passed: true,
errorNo: 0,
errorMessage: 'Permission granted'
};
} else {
return {
passed: false,
errorNo: 1,
errorMessage: 'Permission denied by user'
};
}
}
}

// Usage
const checker = new RandomPrivacyChecker();
gameHandle.setPrivacyChecker({
requirePrivacy: (methodName, callback) => {
checker.requirePrivacy(methodName, callback);
}
});

基于方法名的权限检查​

class MethodBasedPrivacyChecker {
constructor() {
// Define which permissions are granted by default
this.grantedPermissions = new Set([
'startAccelerometer',
'startGyroscope'
]);
}

requirePrivacy(methodName, callback) {
console.log(`Permission check for: ${methodName}`);

if (this.grantedPermissions.has(methodName)) {
// Permission already granted
callback({
passed: true,
errorNo: 0,
errorMessage: ''
});
} else {
// Need to request permission
this.requestPermission(methodName, callback);
}
}

requestPermission(methodName, callback) {
// Simulate user permission dialog
console.log(`Requesting permission for: ${methodName}`);

setTimeout(() => {
// Simulate user response
const userGranted = Math.random() > 0.3;

if (userGranted) {
this.grantedPermissions.add(methodName);
callback({
passed: true,
errorNo: 0,
errorMessage: ''
});
} else {
callback({
passed: false,
errorNo: 10001,
errorMessage: 'User denied permission'
});
}
}, 1000);
}
}

// Usage
const checker = new MethodBasedPrivacyChecker();
gameHandle.setPrivacyChecker({
requirePrivacy: (methodName, callback) => {
checker.requirePrivacy(methodName, callback);
}
});

与系统权限集成​

const { systemPreferences } = require('electron');

class ElectronPrivacyChecker {
async requirePrivacy(methodName, callback) {
try {
const granted = await this.checkSystemPermission(methodName);

if (granted) {
callback({
passed: true,
errorNo: 0,
errorMessage: ''
});
} else {
callback({
passed: false,
errorNo: 10002,
errorMessage: `System permission denied for ${methodName}`
});
}
} catch (error) {
callback({
passed: false,
errorNo: 10003,
errorMessage: error.message
});
}
}

async checkSystemPermission(methodName) {
// Map method names to system permission types
const permissionMap = {
'startRecorderManager': 'microphone',
'startCameraManager': 'camera',
// Add more mappings as needed
};

const permissionType = permissionMap[methodName];

if (!permissionType) {
// No system permission required
return true;
}

// Check if running on macOS (systemPreferences is mainly for macOS)
if (process.platform === 'darwin') {
const status = systemPreferences.getMediaAccessStatus(permissionType);

if (status === 'granted') {
return true;
} else if (status === 'not-determined') {
// Request permission
return await systemPreferences.askForMediaAccess(permissionType);
} else {
return false;
}
}

// For other platforms, assume granted
return true;
}
}

// Usage
const checker = new ElectronPrivacyChecker();
gameHandle.setPrivacyChecker({
requirePrivacy: (methodName, callback) => {
checker.requirePrivacy(methodName, callback);
}
});

完整示例​

const { TJHostHandle } = require('webglhost-runtime-pc');

class PrivacyManager {
constructor() {
this.gameHandle = null;
this.permissionCache = new Map();
this.permissionListeners = new Map();
}

async initialize(hostHandle) {
this.gameHandle = await hostHandle.createGameHandle();
this.setupPrivacyChecker();
}

setupPrivacyChecker() {
const checker = {
requirePrivacy: (methodName, callback) => {
this.handlePrivacyCheck(methodName, callback);
}
};

this.gameHandle.setPrivacyChecker(checker);
console.log('Privacy checker configured');
}

handlePrivacyCheck(methodName, callback) {
console.log(`[Privacy] Check requested: ${methodName}`);

// Check cache first
if (this.permissionCache.has(methodName)) {
const cached = this.permissionCache.get(methodName);
console.log(`[Privacy] Using cached result: ${cached.passed}`);
callback(cached);
return;
}

// Perform permission check
this.checkPermission(methodName)
.then(result => {
// Cache the result
this.permissionCache.set(methodName, result);

// Notify listeners
this.notifyListeners(methodName, result);

// Return result
callback(result);
})
.catch(error => {
console.error(`[Privacy] Check failed:`, error);
callback({
passed: false,
errorNo: 10000,
errorMessage: error.message
});
});
}

async checkPermission(methodName) {
// Implement your permission checking logic here
// This could involve:
// - Checking system permissions (Electron)
// - Showing user dialogs
// - Checking user preferences
// - etc.

console.log(`[Privacy] Checking permission for: ${methodName}`);

// Simulate async permission check
await new Promise(resolve => setTimeout(resolve, 300));

// Example: Camera and microphone require explicit permission
const requiresExplicitPermission = [
'startRecorderManager',
'startCameraManager'
];

if (requiresExplicitPermission.includes(methodName)) {
// In a real app, show permission dialog to user
const granted = await this.showPermissionDialog(methodName);

return {
passed: granted,
errorNo: granted ? 0 : 10001,
errorMessage: granted ? '' : 'Permission denied by user'
};
}

// Other permissions are granted by default
return {
passed: true,
errorNo: 0,
errorMessage: ''
};
}

async showPermissionDialog(methodName) {
// Simulate permission dialog
console.log(`[Privacy] Showing permission dialog for: ${methodName}`);

// In a real app, you would use Electron dialog or custom UI
// For this example, we'll simulate user response
await new Promise(resolve => setTimeout(resolve, 500));

return Math.random() > 0.2; // 80% chance of granting
}

onPermissionChange(methodName, listener) {
if (!this.permissionListeners.has(methodName)) {
this.permissionListeners.set(methodName, []);
}
this.permissionListeners.get(methodName).push(listener);
}

notifyListeners(methodName, result) {
const listeners = this.permissionListeners.get(methodName) || [];
for (const listener of listeners) {
try {
listener(result);
} catch (error) {
console.error('[Privacy] Listener error:', error);
}
}
}

revokePermission(methodName) {
console.log(`[Privacy] Revoking permission: ${methodName}`);
this.permissionCache.delete(methodName);
}

clearAllPermissions() {
console.log('[Privacy] Clearing all permissions');
this.permissionCache.clear();
}

getPermissionStatus(methodName) {
return this.permissionCache.get(methodName) || null;
}
}

// Usage
async function main() {
// Initialize SDK
const hostHandle = await new Promise((resolve, reject) => {
TJHostHandle.initialize('sdk-key', 'sdk-secret', (handle, error) => {
if (error) reject(error);
else resolve(handle);
});
});

// Setup privacy manager
const privacyManager = new PrivacyManager();
await privacyManager.initialize(hostHandle);

// Listen for permission changes
privacyManager.onPermissionChange('startCameraManager', (result) => {
console.log('Camera permission changed:', result);
});

// Start game
await privacyManager.gameHandle.start();
privacyManager.gameHandle.play();

// Later: check permission status
setTimeout(() => {
const cameraStatus = privacyManager.getPermissionStatus('startCameraManager');
console.log('Camera permission status:', cameraStatus);
}, 5000);
}

main().catch(console.error);

最佳实践​

  1. 缓存权限结果:避免重复请求相同权限

  2. 异步处理:权限检查可能涉及用户交互,应该是异步的

  3. 友好提示:在请求权限时向用户解释为什么需要该权限

  4. 处理拒绝:当用户拒绝权限时,提供替代方案或友好提示

  5. 权限最小化:只请求应用真正需要的权限

  6. 平台差异:不同操作系统的权限模型可能不同,需要适配

  7. 错误处理:妥善处理权限检查过程中的各种错误情况