Client with UI
This commit is contained in:
322
docs/README.md
Normal file
322
docs/README.md
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
# FIDO2 UI SDK
|
||||||
|
|
||||||
|
一个简单易用的 FIDO2 设备管理 UI 库,支持快速集成 FIDO2 认证设备管理功能。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 🚀 **快速集成** - 一行代码即可集成设备管理功能
|
||||||
|
- 🎨 **主题定制** - 支持自定义 Logo、颜色、背景等
|
||||||
|
- 🌐 **国际化** - 内置中文、英文、日文支持
|
||||||
|
- 📱 **响应式** - 支持桌面端和移动端
|
||||||
|
- 🎭 **双模式** - 支持弹出窗口和独立页面两种模式
|
||||||
|
- 🎯 **事件回调** - 完整的事件系统,灵活处理各种操作
|
||||||
|
- ♿ **向后兼容** - 完全兼容现有的对接方式
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 引入依赖
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 必需的依赖 -->
|
||||||
|
<script src="files/jquery.js"></script>
|
||||||
|
<script src="files/bootstrap.js"></script>
|
||||||
|
<script src="files/dfido2-lib.js"></script>
|
||||||
|
|
||||||
|
<!-- 引入 SDK -->
|
||||||
|
<script src="files/fido2-ui-sdk.js"></script>
|
||||||
|
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 添加容器(仅模态框模式需要)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div id="device-container"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 调用 SDK
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: 'https://fido2.amipro.me'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用模式
|
||||||
|
|
||||||
|
### 模式 1: 弹出窗口 (推荐)
|
||||||
|
|
||||||
|
弹出窗口模式在当前页面上显示模态框,用户操作完成后自动关闭。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: 'https://fido2.amipro.me'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 用户在设置页面需要管理设备
|
||||||
|
- 不希望离开当前页面
|
||||||
|
- 快速查看和管理设备
|
||||||
|
|
||||||
|
### 模式 2: 独立页面
|
||||||
|
|
||||||
|
独立页面模式会渲染完整的设备管理页面,适合作为独立的页面功能。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
mode: 'standalone',
|
||||||
|
serverUrl: 'https://fido2.amipro.me'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 需要独立的设备管理页面
|
||||||
|
- 通过链接直接访问设备管理
|
||||||
|
- 需要更多屏幕空间显示设备信息
|
||||||
|
|
||||||
|
## 配置选项
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
// === 核心配置 ===
|
||||||
|
serverUrl: 'https://fido2.amipro.me', // FIDO2 服务器地址
|
||||||
|
mode: 'modal', // 'modal' | 'standalone'
|
||||||
|
|
||||||
|
// === 容器配置 (仅 modal 模式需要) ===
|
||||||
|
container: '#device-container', // 容器选择器或 DOM 元素
|
||||||
|
|
||||||
|
// === 主题定制 ===
|
||||||
|
theme: {
|
||||||
|
logo: 'path/to/logo.png', // Logo URL
|
||||||
|
primaryColor: '#ce59d9', // 主色调
|
||||||
|
backgroundColor: '#ffffff', // 背景色
|
||||||
|
textColor: '#333333', // 文字颜色
|
||||||
|
borderRadius: '8px', // 圆角
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 国际化 ===
|
||||||
|
language: 'zh-CN', // 'en-US' | 'zh-CN' | 'ja'
|
||||||
|
customI18n: { // 自定义翻译
|
||||||
|
'my_devices': { 'zh-CN': '我的设备' },
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 功能开关 ===
|
||||||
|
features: {
|
||||||
|
showAddButton: true,
|
||||||
|
showDeleteButton: true,
|
||||||
|
showUserInfo: true,
|
||||||
|
showSessionStatus: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 事件回调 ===
|
||||||
|
callbacks: {
|
||||||
|
onInit: () => {}, // 初始化完成
|
||||||
|
onDeviceAdded: (device) => {}, // 设备添加成功
|
||||||
|
onDeviceDeleted: (deviceId) => {}, // 设备删除成功
|
||||||
|
onDeviceListLoaded: (devices) => {}, // 设备列表加载完成
|
||||||
|
onError: (error) => {}, // 错误发生
|
||||||
|
onClose: () => {}, // 窗口关闭 (仅 modal 模式)
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 其他 ===
|
||||||
|
rpId: null, // Relying Party ID
|
||||||
|
autoRefresh: true, // 自动刷新设备列表
|
||||||
|
refreshInterval: 5000, // 刷新间隔(ms)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
### 示例 1: 基础集成
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="files/jquery.js"></script>
|
||||||
|
<script src="files/bootstrap.js"></script>
|
||||||
|
<script src="files/dfido2-lib.js"></script>
|
||||||
|
<script src="files/fido2-ui-sdk.js"></script>
|
||||||
|
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button onclick="openDeviceManager()">管理设备</button>
|
||||||
|
<div id="device-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openDeviceManager() {
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: 'https://fido2.amipro.me'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2: 带主题定制
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: 'https://fido2.amipro.me',
|
||||||
|
theme: {
|
||||||
|
logo: '/assets/my-logo.png',
|
||||||
|
primaryColor: '#0066cc',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
textColor: '#333333',
|
||||||
|
borderRadius: '12px'
|
||||||
|
},
|
||||||
|
language: 'zh-CN'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3: 带事件回调
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: 'https://fido2.amipro.me',
|
||||||
|
callbacks: {
|
||||||
|
onInit: function(manager) {
|
||||||
|
console.log('设备管理器初始化完成');
|
||||||
|
},
|
||||||
|
onDeviceAdded: function(device) {
|
||||||
|
console.log('设备添加成功:', device);
|
||||||
|
// 刷新用户界面或其他逻辑
|
||||||
|
},
|
||||||
|
onDeviceDeleted: function(deviceId) {
|
||||||
|
console.log('设备删除成功:', deviceId);
|
||||||
|
},
|
||||||
|
onDeviceListLoaded: function(devices) {
|
||||||
|
console.log('设备列表加载完成:', devices.length, '个设备');
|
||||||
|
},
|
||||||
|
onError: function(error) {
|
||||||
|
console.error('发生错误:', error);
|
||||||
|
},
|
||||||
|
onClose: function() {
|
||||||
|
console.log('窗口关闭');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 4: 自定义翻译
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: 'https://fido2.amipro.me',
|
||||||
|
language: 'zh-CN',
|
||||||
|
customI18n: {
|
||||||
|
'my_devices': {
|
||||||
|
'zh-CN': '我的 FIDO2 设备'
|
||||||
|
},
|
||||||
|
'btn_add': {
|
||||||
|
'zh-CN': '添加新设备'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 方法
|
||||||
|
|
||||||
|
### renderDeviceManager(config)
|
||||||
|
|
||||||
|
渲染设备管理器。
|
||||||
|
|
||||||
|
**参数:**
|
||||||
|
- `config` (Object) - 配置对象
|
||||||
|
|
||||||
|
**返回:**
|
||||||
|
- (Object) - 设备管理器实例
|
||||||
|
|
||||||
|
### close()
|
||||||
|
|
||||||
|
关闭模态框(仅 modal 模式)。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.close();
|
||||||
|
```
|
||||||
|
|
||||||
|
### refresh()
|
||||||
|
|
||||||
|
刷新设备列表和会话状态。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.refresh();
|
||||||
|
```
|
||||||
|
|
||||||
|
### destroy()
|
||||||
|
|
||||||
|
销毁设备管理器实例。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Fido2UIManager.destroy();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 事件系统
|
||||||
|
|
||||||
|
SDK 提供以下事件:
|
||||||
|
|
||||||
|
| 事件名称 | 触发时机 | 回调参数 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `init` | 初始化完成 | (manager) |
|
||||||
|
| `deviceAdded` | 设备添加成功 | (device) |
|
||||||
|
| `deviceDeleted` | 设备删除成功 | (deviceId) |
|
||||||
|
| `deviceListLoaded` | 设备列表加载完成 | (devices) |
|
||||||
|
| `sessionStatusChanged` | 会话状态改变 | (isValid) |
|
||||||
|
| `error` | 发生错误 | (error) |
|
||||||
|
| `close` | 窗口关闭 | 无 |
|
||||||
|
|
||||||
|
## 向后兼容性
|
||||||
|
|
||||||
|
SDK 完全兼容现有的对接方式:
|
||||||
|
|
||||||
|
- ✅ `devices.html` - 继续使用现有逻辑,无需修改
|
||||||
|
- ✅ `login.html` - 继续使用现有逻辑,无需修改
|
||||||
|
- ✅ `dfido2-lib.js` - 核心库保持不变
|
||||||
|
- ✅ 所有 CSS 文件 - 保持原样
|
||||||
|
|
||||||
|
## 浏览器支持
|
||||||
|
|
||||||
|
- Chrome 67+
|
||||||
|
- Firefox 60+
|
||||||
|
- Safari 13+
|
||||||
|
- Edge 18+
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
- jQuery 3.x
|
||||||
|
- Bootstrap 5.x
|
||||||
|
- dfido2-lib.js(核心 FIDO2 库)
|
||||||
|
|
||||||
|
## 演示
|
||||||
|
|
||||||
|
- [模态框模式演示](modal-demo.html)
|
||||||
|
- [独立页面模式演示](standalone-demo.html)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 确保 `dfido2-lib.js` 在 `fido2-ui-sdk.js` 之前加载
|
||||||
|
2. Modal 模式需要提供有效的容器选择器
|
||||||
|
3. 独立页面模式会替换当前页面的内容
|
||||||
|
4. 确保浏览器支持 WebAuthn API
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
[根据您的项目许可证填写]
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题或建议,请联系 [您的联系方式]
|
||||||
@@ -78,6 +78,9 @@ async function listUserDevicesFido2(rpId = null) {
|
|||||||
const resp = await response.json();
|
const resp = await response.json();
|
||||||
if ('ok' === resp.status && resp.session === session_data.session) {
|
if ('ok' === resp.status && resp.session === session_data.session) {
|
||||||
return {status:'ok', devices:resp.devices}
|
return {status:'ok', devices:resp.devices}
|
||||||
|
} else if (resp.errorMessage && resp.errorMessage.includes('No user session')) {
|
||||||
|
sessionStorage.removeItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION);
|
||||||
|
return {status:'ok', devices:[]}
|
||||||
} else {
|
} else {
|
||||||
return {status:'failed', errorMessage: resp.errorMessage}
|
return {status:'failed', errorMessage: resp.errorMessage}
|
||||||
}
|
}
|
||||||
@@ -374,8 +377,8 @@ function makePublicKey(attOptsResp) {
|
|||||||
rp: attOptsResp.rp,
|
rp: attOptsResp.rp,
|
||||||
user: {
|
user: {
|
||||||
id: _stringToArrayBuffer(attOptsResp.user.id), //_base64ToArrayBuffer(_fromBase64URL(attOptsResp.user.id)),
|
id: _stringToArrayBuffer(attOptsResp.user.id), //_base64ToArrayBuffer(_fromBase64URL(attOptsResp.user.id)),
|
||||||
name: attOptsResp.user.name,
|
name: attOptsResp.user.name || attOptsResp.user.id || 'user',
|
||||||
displayName: attOptsResp.user.displayName,
|
displayName: attOptsResp.user.displayName || attOptsResp.user.name || attOptsResp.user.id || 'User',
|
||||||
},
|
},
|
||||||
pubKeyCredParams: attOptsResp.pubKeyCredParams,
|
pubKeyCredParams: attOptsResp.pubKeyCredParams,
|
||||||
timeout: attOptsResp.timeout,
|
timeout: attOptsResp.timeout,
|
||||||
@@ -387,6 +390,10 @@ function makePublicKey(attOptsResp) {
|
|||||||
|
|
||||||
async function doAttestation(username, displayName, rpId, userVerification = 'preferred') {
|
async function doAttestation(username, displayName, rpId, userVerification = 'preferred') {
|
||||||
var process_time_limit = Number.MAX_SAFE_INTEGER
|
var process_time_limit = Number.MAX_SAFE_INTEGER
|
||||||
|
if (window._fido2_pending_request) {
|
||||||
|
return {status:'failed', errorMessage: 'Fido2LibErr106:A request is already pending'};
|
||||||
|
}
|
||||||
|
window._fido2_pending_request = true;
|
||||||
try {
|
try {
|
||||||
const attestationOptions = {
|
const attestationOptions = {
|
||||||
username: username,
|
username: username,
|
||||||
@@ -474,11 +481,17 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
|||||||
}else errRtn.errCode = fido2LibErrCodes.unknown
|
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||||
|
|
||||||
return errRtn;
|
return errRtn;
|
||||||
|
} finally {
|
||||||
|
window._fido2_pending_request = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doAssertion(username = null, rpId = null, userVerification = 'preferred') {
|
async function doAssertion(username = null, rpId = null, userVerification = 'preferred') {
|
||||||
var process_time_limit = Number.MAX_SAFE_INTEGER
|
var process_time_limit = Number.MAX_SAFE_INTEGER
|
||||||
|
if (window._fido2_pending_request) {
|
||||||
|
return {status:'failed', errorMessage: 'Fido2LibErr106:A request is already pending'};
|
||||||
|
}
|
||||||
|
window._fido2_pending_request = true;
|
||||||
try {
|
try {
|
||||||
let authnOptions;
|
let authnOptions;
|
||||||
/*
|
/*
|
||||||
@@ -586,6 +599,8 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
|||||||
}else errRtn.errCode = fido2LibErrCodes.unknown
|
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||||
|
|
||||||
return errRtn;
|
return errRtn;
|
||||||
|
} finally {
|
||||||
|
window._fido2_pending_request = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
276
files/fido2-ui-sdk.css
Normal file
276
files/fido2-ui-sdk.css
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
.fido2-sdk-modal .modal-dialog {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-modal .modal-content {
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-header {
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-logo {
|
||||||
|
max-height: 40px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-container {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-body {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-text {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-table th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-table td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn {
|
||||||
|
padding: 10px 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn-primary {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-status-badge {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-user-info {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
border-left: 4px solid #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-standalone {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-standalone .container {
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-standalone .card {
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-standalone .card-header {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-standalone .card-body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-table .text-danger {
|
||||||
|
color: #dc3545 !important;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-table .text-danger:hover {
|
||||||
|
color: #c82333 !important;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.fido2-sdk-modal .modal-dialog {
|
||||||
|
margin: 10px;
|
||||||
|
max-width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-standalone {
|
||||||
|
padding: 20px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-table th,
|
||||||
|
.fido2-sdk-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-logo {
|
||||||
|
max-height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-loading {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: #fff;
|
||||||
|
animation: fido2-sdk-spin 1s ease-in-out infinite;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fido2-sdk-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-device-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
color: #0d6efd;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-empty-state i {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-empty-state p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-tooltip {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-tooltip::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s, visibility 0.2s;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-tooltip:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-btn-group .btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fido2AddDeviceBtn {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-alert {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-left: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-alert-success {
|
||||||
|
background-color: #d4edda;
|
||||||
|
border-color: #28a745;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-alert-error {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-color: #dc3545;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-alert-info {
|
||||||
|
background-color: #d1ecf1;
|
||||||
|
border-color: #17a2b8;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fido2-sdk-alert-warning {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-color: #ffc107;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
963
files/fido2-ui-sdk.js
Normal file
963
files/fido2-ui-sdk.js
Normal file
@@ -0,0 +1,963 @@
|
|||||||
|
(function(window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const FIDO2_UI_VERSION = '1.0.0';
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
serverUrl: 'https://fido2.amipro.me',
|
||||||
|
mode: 'modal',
|
||||||
|
container: null,
|
||||||
|
theme: {
|
||||||
|
logo: null,
|
||||||
|
primaryColor: '#696cff',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
textColor: '#333333',
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
language: 'zh-CN',
|
||||||
|
customI18n: {},
|
||||||
|
features: {
|
||||||
|
showAddButton: true,
|
||||||
|
showDeleteButton: true,
|
||||||
|
showUserInfo: true,
|
||||||
|
showSessionStatus: true,
|
||||||
|
},
|
||||||
|
callbacks: {},
|
||||||
|
rpId: null,
|
||||||
|
autoRefresh: true,
|
||||||
|
refreshInterval: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_I18N = {
|
||||||
|
'my_devices': {
|
||||||
|
'en-US': 'My devices',
|
||||||
|
'zh-CN': '我的设备',
|
||||||
|
'ja': 'マイデバイス'
|
||||||
|
},
|
||||||
|
'btn_add': {
|
||||||
|
'en-US': 'Add device',
|
||||||
|
'zh-CN': '添加设备',
|
||||||
|
'ja': 'デバイスを追加'
|
||||||
|
},
|
||||||
|
'title_device': {
|
||||||
|
'en-US': 'Device',
|
||||||
|
'zh-CN': '设备',
|
||||||
|
'ja': 'デバイス'
|
||||||
|
},
|
||||||
|
'title_time': {
|
||||||
|
'en-US': 'Registered time',
|
||||||
|
'zh-CN': '添加时间',
|
||||||
|
'ja': '登録時間'
|
||||||
|
},
|
||||||
|
'title_act': {
|
||||||
|
'en-US': 'Actions',
|
||||||
|
'zh-CN': '操作',
|
||||||
|
'ja': '操作'
|
||||||
|
},
|
||||||
|
'title_del': {
|
||||||
|
'en-US': 'Delete',
|
||||||
|
'zh-CN': '删除',
|
||||||
|
'ja': '削除'
|
||||||
|
},
|
||||||
|
'title_logout': {
|
||||||
|
'en-US': 'Log out',
|
||||||
|
'zh-CN': '登出',
|
||||||
|
'ja': 'ログアウト'
|
||||||
|
},
|
||||||
|
'title_empty_list': {
|
||||||
|
'en-US': 'No devices, please add.',
|
||||||
|
'zh-CN': '无设备,请添加。',
|
||||||
|
'ja': 'デバイスがなし、追加してください。'
|
||||||
|
},
|
||||||
|
'msg_register_ok': {
|
||||||
|
'en-US': 'Device registered successfully',
|
||||||
|
'zh-CN': '添加设备成功',
|
||||||
|
'ja': 'デバイス登録完了'
|
||||||
|
},
|
||||||
|
'msg_deldev_ok': {
|
||||||
|
'en-US': 'Device deleted successfully',
|
||||||
|
'zh-CN': '设备删除成功',
|
||||||
|
'ja': 'デバイスを削除しました'
|
||||||
|
},
|
||||||
|
'msg_confirm_deldev': {
|
||||||
|
'en-US': 'Do you want to delete this device?',
|
||||||
|
'zh-CN': '确认删除此设备吗?',
|
||||||
|
'ja': 'デバイスを削除しますか?'
|
||||||
|
},
|
||||||
|
'msg_session_status_ok': {
|
||||||
|
'en-US': 'FIDO2 session is valid',
|
||||||
|
'zh-CN': 'FIDO2会话正常',
|
||||||
|
'ja': 'FIDO2セッションは正常です'
|
||||||
|
},
|
||||||
|
'msg_session_status_fail': {
|
||||||
|
'en-US': 'FIDO2 session is invalid',
|
||||||
|
'zh-CN': 'FIDO2会话无效',
|
||||||
|
'ja': 'FIDO2セッションは無効です'
|
||||||
|
},
|
||||||
|
'btn_close': {
|
||||||
|
'en-US': 'Close',
|
||||||
|
'zh-CN': '关闭',
|
||||||
|
'ja': '閉じる'
|
||||||
|
},
|
||||||
|
'title_welcome': {
|
||||||
|
'en-US': 'Welcome',
|
||||||
|
'zh-CN': '欢迎',
|
||||||
|
'ja': 'ようこそ'
|
||||||
|
},
|
||||||
|
'btn_login': {
|
||||||
|
'en-US': 'Login',
|
||||||
|
'zh-CN': '重新登录',
|
||||||
|
'ja': 'ログイン'
|
||||||
|
},
|
||||||
|
'msg_session_invalid': {
|
||||||
|
'en-US': 'Session expired, please login again',
|
||||||
|
'zh-CN': '会话已过期,请重新登录',
|
||||||
|
'ja': 'セッション切れ、再ログインしてください'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function I18nManager(config) {
|
||||||
|
this.config = config;
|
||||||
|
this.messages = new Map();
|
||||||
|
|
||||||
|
for (let key in DEFAULT_I18N) {
|
||||||
|
this.messages.set(key, new Map());
|
||||||
|
for (let lang in DEFAULT_I18N[key]) {
|
||||||
|
this.messages.get(key).set(lang, DEFAULT_I18N[key][lang]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.customI18n) {
|
||||||
|
for (let key in config.customI18n) {
|
||||||
|
if (!this.messages.has(key)) {
|
||||||
|
this.messages.set(key, new Map());
|
||||||
|
}
|
||||||
|
for (let lang in config.customI18n[key]) {
|
||||||
|
this.messages.get(key).set(lang, config.customI18n[key][lang]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I18nManager.prototype.getText = function(key) {
|
||||||
|
const lang = this.config.language || window.navigator.language || 'en-US';
|
||||||
|
const keyMap = this.messages.get(key);
|
||||||
|
|
||||||
|
if (!keyMap) return key;
|
||||||
|
|
||||||
|
let text = keyMap.get(lang);
|
||||||
|
if (!text) text = keyMap.get('en-US');
|
||||||
|
|
||||||
|
return text || key;
|
||||||
|
};
|
||||||
|
|
||||||
|
function EventManager() {
|
||||||
|
this.callbacks = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
EventManager.prototype.on = function(event, callback) {
|
||||||
|
if (!this.callbacks[event]) {
|
||||||
|
this.callbacks[event] = [];
|
||||||
|
}
|
||||||
|
this.callbacks[event].push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
EventManager.prototype.off = function(event, callback) {
|
||||||
|
if (!this.callbacks[event]) return;
|
||||||
|
|
||||||
|
const index = this.callbacks[event].indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
this.callbacks[event].splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EventManager.prototype.emit = function(event, data) {
|
||||||
|
if (!this.callbacks[event]) return;
|
||||||
|
|
||||||
|
this.callbacks[event].forEach(function(callback) {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function ThemeManager(config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeManager.prototype.applyTheme = function(element) {
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const theme = this.config.theme;
|
||||||
|
const styleId = 'fido2-ui-sdk-theme-' + Date.now();
|
||||||
|
|
||||||
|
let css = '';
|
||||||
|
if (theme.primaryColor) {
|
||||||
|
css += '.fido2-sdk-btn-primary { background-color: ' + theme.primaryColor + '!important; border-color: ' + theme.primaryColor + '!important; }';
|
||||||
|
css += '.fido2-sdk-header { border-bottom: 2px solid ' + theme.primaryColor + '!important; }';
|
||||||
|
}
|
||||||
|
if (theme.backgroundColor) {
|
||||||
|
css += '.fido2-sdk-container { background-color: ' + theme.backgroundColor + '!important; }';
|
||||||
|
}
|
||||||
|
if (theme.textColor) {
|
||||||
|
css += '.fido2-sdk-text { color: ' + theme.textColor + '!important; }';
|
||||||
|
}
|
||||||
|
if (theme.borderRadius) {
|
||||||
|
css += '.fido2-sdk-card, .fido2-sdk-btn { border-radius: ' + theme.borderRadius + '!important; }';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (css) {
|
||||||
|
let style = document.getElementById(styleId);
|
||||||
|
if (style) {
|
||||||
|
style.parentNode.removeChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
style = document.createElement('style');
|
||||||
|
style.id = styleId;
|
||||||
|
style.textContent = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.dataset.themeStyleId = styleId;
|
||||||
|
};
|
||||||
|
|
||||||
|
ThemeManager.prototype.cleanup = function(element) {
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const styleId = element.dataset.themeStyleId;
|
||||||
|
if (styleId) {
|
||||||
|
const style = document.getElementById(styleId);
|
||||||
|
if (style) {
|
||||||
|
style.parentNode.removeChild(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function DeviceManager(config, i18n, eventManager) {
|
||||||
|
this.config = config;
|
||||||
|
this.i18n = i18n;
|
||||||
|
this.eventManager = eventManager;
|
||||||
|
this.devices = [];
|
||||||
|
this.refreshTimer = null;
|
||||||
|
this.sessionStatus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceManager.prototype.loadDevices = async function() {
|
||||||
|
try {
|
||||||
|
const result = await listUserDevicesFido2(this.config.rpId);
|
||||||
|
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
this.devices = result.devices || [];
|
||||||
|
this.eventManager.emit('deviceListLoaded', this.devices);
|
||||||
|
return this.devices;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.errorMessage || 'Failed to load devices');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.eventManager.emit('error', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.addDevice = async function(displayName) {
|
||||||
|
try {
|
||||||
|
const sessionData = this.getSessionData();
|
||||||
|
if (!sessionData) {
|
||||||
|
throw new Error('No session data available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = sessionData.uid;
|
||||||
|
const result = await registerFido2(userId, displayName || 'Device-' + userId, this.config.rpId);
|
||||||
|
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
await this.loadDevices();
|
||||||
|
this.eventManager.emit('deviceAdded', result);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.errorMessage || 'Failed to add device');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.eventManager.emit('error', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.deleteDevice = async function(deviceId) {
|
||||||
|
try {
|
||||||
|
const result = await delUserDeviceFido2(deviceId, this.config.rpId);
|
||||||
|
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
await this.loadDevices();
|
||||||
|
this.eventManager.emit('deviceDeleted', deviceId);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.errorMessage || 'Failed to delete device');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.eventManager.emit('error', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.getSessionData = function() {
|
||||||
|
try {
|
||||||
|
const sessionText = sessionStorage.getItem('fido2_user_session');
|
||||||
|
if (!sessionText) return null;
|
||||||
|
|
||||||
|
return JSON.parse(sessionText);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.getUserId = function() {
|
||||||
|
const sessionData = this.getSessionData();
|
||||||
|
return sessionData ? sessionData.uid : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.checkSession = async function() {
|
||||||
|
try {
|
||||||
|
this.sessionStatus = await validSession(this.config.rpId);
|
||||||
|
this.eventManager.emit('sessionStatusChanged', this.sessionStatus);
|
||||||
|
return this.sessionStatus;
|
||||||
|
} catch (error) {
|
||||||
|
this.sessionStatus = false;
|
||||||
|
this.eventManager.emit('sessionStatusChanged', false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.parseDeviceDescription = function(device) {
|
||||||
|
if (device.desc && device.desc.length > 0) {
|
||||||
|
return device.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (device.userAgent && typeof UAParser !== 'undefined') {
|
||||||
|
const parser = new UAParser(device.userAgent);
|
||||||
|
if (parser.getOS().name) {
|
||||||
|
return parser.getDevice().model + ',' + parser.getOS().name + ',' + parser.getBrowser().name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return device.userAgent || 'Unknown Device';
|
||||||
|
} catch (error) {
|
||||||
|
return 'Unknown Device';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.startAutoRefresh = function() {
|
||||||
|
if (!this.config.autoRefresh) return;
|
||||||
|
|
||||||
|
this.stopAutoRefresh();
|
||||||
|
|
||||||
|
this.refreshTimer = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await this.loadDevices();
|
||||||
|
await this.checkSession();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Auto refresh error:', error);
|
||||||
|
}
|
||||||
|
}, this.config.refreshInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.stopAutoRefresh = function() {
|
||||||
|
if (this.refreshTimer) {
|
||||||
|
clearInterval(this.refreshTimer);
|
||||||
|
this.refreshTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function UIRenderer(config, i18n, themeManager, eventManager) {
|
||||||
|
this.config = config;
|
||||||
|
this.i18n = i18n;
|
||||||
|
this.themeManager = themeManager;
|
||||||
|
this.eventManager = eventManager;
|
||||||
|
this.modalElement = null;
|
||||||
|
this.containerElement = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIRenderer.prototype.renderModal = function() {
|
||||||
|
if (!this.config.container) {
|
||||||
|
throw new Error('Container is required for modal mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = typeof this.config.container === 'string'
|
||||||
|
? document.querySelector(this.config.container)
|
||||||
|
: this.config.container;
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
throw new Error('Container not found: ' + this.config.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'modal fade fido2-sdk-modal';
|
||||||
|
modal.id = 'fido2SdkModal';
|
||||||
|
modal.tabIndex = -1;
|
||||||
|
modal.setAttribute('aria-hidden', 'true');
|
||||||
|
|
||||||
|
modal.innerHTML = this._getModalHTML();
|
||||||
|
container.appendChild(modal);
|
||||||
|
this.modalElement = modal;
|
||||||
|
this.containerElement = container;
|
||||||
|
|
||||||
|
this.themeManager.applyTheme(modal);
|
||||||
|
|
||||||
|
const bootstrapModal = new window.bootstrap.Modal(modal, {
|
||||||
|
backdrop: true,
|
||||||
|
keyboard: true
|
||||||
|
});
|
||||||
|
|
||||||
|
bootstrapModal.show();
|
||||||
|
|
||||||
|
modal.addEventListener('hidden.bs.modal', () => {
|
||||||
|
this.eventManager.emit('close');
|
||||||
|
this.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._bindEvents();
|
||||||
|
|
||||||
|
return modal;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype._getModalHTML = function() {
|
||||||
|
const theme = this.config.theme;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
|
<div class="modal-content fido2-sdk-card">
|
||||||
|
<div class="modal-header fido2-sdk-header">
|
||||||
|
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo" alt="Logo">` : ''}
|
||||||
|
<h5 class="modal-title fido2-sdk-text">${this.i18n.getText('my_devices')}</h5>
|
||||||
|
${this.config.features.showSessionStatus ? '<span class="badge fido2-sdk-status-badge" id="fido2SessionStatus"></span>' : ''}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body fido2-sdk-container">
|
||||||
|
${this._getBodyHTML()}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary fido2-sdk-btn" data-bs-dismiss="modal">${this.i18n.getText('btn_close')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype._getBodyHTML = function() {
|
||||||
|
const features = this.config.features;
|
||||||
|
const userId = window.Fido2UIManager.deviceManager ? window.Fido2UIManager.deviceManager.getUserId() : '';
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if (features.showUserInfo && userId) {
|
||||||
|
html += `<div class="fido2-sdk-user-info mb-3 fido2-sdk-text">
|
||||||
|
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<div id="fido2SessionAlert" class="alert alert-warning mb-3 fido2-sdk-text" style="display:none;">
|
||||||
|
${this.i18n.getText('msg_session_invalid')}
|
||||||
|
<button type="button" class="btn btn-sm btn-primary ms-2" id="fido2LoginBtn">
|
||||||
|
${this.i18n.getText('btn_login')}
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
if (features.showAddButton) {
|
||||||
|
html += `
|
||||||
|
<button type="button" class="btn btn-info mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2AddDeviceBtn">
|
||||||
|
${this.i18n.getText('btn_add')}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped fido2-sdk-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>${this.i18n.getText('title_device')}</th>
|
||||||
|
<th>${this.i18n.getText('title_time')}</th>
|
||||||
|
${features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="fido2DevicesList">
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype._bindEvents = function() {
|
||||||
|
const container = this.modalElement || this.containerElement;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.querySelectorAll('[data-fido2-action="delete"]').forEach(btn => {
|
||||||
|
btn.replaceWith(btn.cloneNode(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
const addBtn = container.querySelector('#fido2AddDeviceBtn');
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.replaceWith(addBtn.cloneNode(true));
|
||||||
|
const newAddBtn = container.querySelector('#fido2AddDeviceBtn');
|
||||||
|
newAddBtn.addEventListener('click', () => {
|
||||||
|
this.eventManager.emit('addDevice');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginBtn = container.querySelector('#fido2LoginBtn');
|
||||||
|
if (loginBtn) {
|
||||||
|
loginBtn.replaceWith(loginBtn.cloneNode(true));
|
||||||
|
const newLoginBtn = container.querySelector('#fido2LoginBtn');
|
||||||
|
newLoginBtn.addEventListener('click', () => {
|
||||||
|
this.eventManager.emit('login');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
container.querySelectorAll('[data-fido2-action="delete"]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const deviceId = btn.dataset.deviceId;
|
||||||
|
if (deviceId && confirm(this.i18n.getText('msg_confirm_deldev'))) {
|
||||||
|
this.eventManager.emit('deleteDevice', deviceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype.updateDevicesList = function(devices) {
|
||||||
|
const tbody = document.getElementById('fido2DevicesList');
|
||||||
|
if (!tbody) return;
|
||||||
|
|
||||||
|
if (!devices || devices.length === 0) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
||||||
|
|
||||||
|
devices.forEach(device => {
|
||||||
|
const desc = dm ? dm.parseDeviceDescription(device) : device.desc || 'Unknown Device';
|
||||||
|
const date = new Date(device.registered_time).toLocaleString();
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td class="fido2-sdk-text"><strong>${desc}</strong></td>
|
||||||
|
<td class="fido2-sdk-text">${date}</td>
|
||||||
|
${this.config.features.showDeleteButton ? `
|
||||||
|
<td>
|
||||||
|
<a href="javascript:void(0)"
|
||||||
|
class="text-danger"
|
||||||
|
data-fido2-action="delete"
|
||||||
|
data-device-id="${device.device_id}">
|
||||||
|
<i class="bx bx-trash"></i> ${this.i18n.getText('title_del')}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
` : ''}
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
this._bindEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype.updateSessionStatus = function(isValid) {
|
||||||
|
const badge = document.getElementById('fido2SessionStatus');
|
||||||
|
const alertDiv = document.getElementById('fido2SessionAlert');
|
||||||
|
const addBtn = document.getElementById('fido2AddDeviceBtn');
|
||||||
|
|
||||||
|
if (badge) {
|
||||||
|
if (isValid) {
|
||||||
|
badge.className = 'badge bg-success fido2-sdk-status-badge';
|
||||||
|
badge.textContent = this.i18n.getText('msg_session_status_ok');
|
||||||
|
} else {
|
||||||
|
badge.className = 'badge bg-danger fido2-sdk-status-badge';
|
||||||
|
badge.textContent = this.i18n.getText('msg_session_status_fail');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alertDiv) {
|
||||||
|
alertDiv.style.display = isValid ? 'none' : 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.disabled = !isValid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype.close = function() {
|
||||||
|
if (this.modalElement) {
|
||||||
|
const bootstrapModal = window.bootstrap.Modal.getInstance(this.modalElement);
|
||||||
|
if (bootstrapModal) {
|
||||||
|
bootstrapModal.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype.cleanup = function() {
|
||||||
|
if (this.modalElement) {
|
||||||
|
this.themeManager.cleanup(this.modalElement);
|
||||||
|
if (this.modalElement.parentNode) {
|
||||||
|
this.modalElement.parentNode.removeChild(this.modalElement);
|
||||||
|
}
|
||||||
|
this.modalElement = null;
|
||||||
|
}
|
||||||
|
this.containerElement = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype.renderStandalone = function() {
|
||||||
|
if (this.config.container) {
|
||||||
|
const container = typeof this.config.container === 'string'
|
||||||
|
? document.querySelector(this.config.container)
|
||||||
|
: this.config.container;
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = this._getStandaloneBodyHTML();
|
||||||
|
this.containerElement = container;
|
||||||
|
this.themeManager.applyTheme(container);
|
||||||
|
this._bindEvents();
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.innerHTML = this._getStandaloneBodyHTML();
|
||||||
|
document.body.className = 'fido2-sdk-standalone';
|
||||||
|
this.containerElement = document.body;
|
||||||
|
this.themeManager.applyTheme(document.body);
|
||||||
|
this._bindEvents();
|
||||||
|
return document.body;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype._getStandaloneBodyHTML = function() {
|
||||||
|
const theme = this.config.theme;
|
||||||
|
const userId = window.Fido2UIManager.deviceManager ? window.Fido2UIManager.deviceManager.getUserId() : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="container fido2-sdk-container">
|
||||||
|
<div class="card fido2-sdk-card">
|
||||||
|
<div class="card-header fido2-sdk-header d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo me-2" alt="Logo">` : ''}
|
||||||
|
<h4 class="mb-0 fido2-sdk-text">${this.i18n.getText('my_devices')}</h4>
|
||||||
|
</div>
|
||||||
|
${this.config.features.showSessionStatus ? '<span class="badge fido2-sdk-status-badge" id="fido2SessionStatus"></span>' : ''}
|
||||||
|
</div>
|
||||||
|
<div class="card-body fido2-sdk-body">
|
||||||
|
${this.config.features.showUserInfo && userId ? `
|
||||||
|
<div class="alert alert-info fido2-sdk-user-info fido2-sdk-text">
|
||||||
|
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
<div id="fido2SessionAlert" class="alert alert-warning mb-3 fido2-sdk-text" style="display:none;">
|
||||||
|
${this.i18n.getText('msg_session_invalid')}
|
||||||
|
<button type="button" class="btn btn-sm btn-primary ms-2" id="fido2LoginBtn">
|
||||||
|
${this.i18n.getText('btn_login')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
${this.config.features.showAddButton ? `
|
||||||
|
<button type="button" class="btn btn-info mt-2 mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2AddDeviceBtn">
|
||||||
|
${this.i18n.getText('btn_add')}
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
<div class="table-responsive mt-2">
|
||||||
|
<table class="table table-striped fido2-sdk-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>${this.i18n.getText('title_device')}</th>
|
||||||
|
<th>${this.i18n.getText('title_time')}</th>
|
||||||
|
${this.config.features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="fido2DevicesList">
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype._getStandaloneHTML = function() {
|
||||||
|
const theme = this.config.theme;
|
||||||
|
const userId = window.Fido2UIManager.deviceManager ? window.Fido2UIManager.deviceManager.getUserId() : '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>${this.i18n.getText('my_devices')}</title>
|
||||||
|
<link rel="stylesheet" href="files/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="files/boxicons.css">
|
||||||
|
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||||
|
</head>
|
||||||
|
<body class="fido2-sdk-standalone">
|
||||||
|
<div class="container fido2-sdk-container">
|
||||||
|
<div class="card fido2-sdk-card">
|
||||||
|
<div class="card-header fido2-sdk-header d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo me-2" alt="Logo">` : ''}
|
||||||
|
<h4 class="mb-0 fido2-sdk-text">${this.i18n.getText('my_devices')}</h4>
|
||||||
|
</div>
|
||||||
|
${this.config.features.showSessionStatus ? '<span class="badge fido2-sdk-status-badge" id="fido2SessionStatus"></span>' : ''}
|
||||||
|
</div>
|
||||||
|
<div class="card-body fido2-sdk-body">
|
||||||
|
${this.config.features.showUserInfo && userId ? `
|
||||||
|
<div class="alert alert-info fido2-sdk-user-info fido2-sdk-text">
|
||||||
|
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${this.config.features.showAddButton ? `
|
||||||
|
<button type="button" class="btn btn-info mt-2 mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2AddDeviceBtn">
|
||||||
|
${this.i18n.getText('btn_add')}
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
<div class="table-responsive mt-2">
|
||||||
|
<table class="table table-striped fido2-sdk-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>${this.i18n.getText('title_device')}</th>
|
||||||
|
<th>${this.i18n.getText('title_time')}</th>
|
||||||
|
${this.config.features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="fido2DevicesList">
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Fido2UIManager() {
|
||||||
|
this.config = null;
|
||||||
|
this.i18n = null;
|
||||||
|
this.eventManager = null;
|
||||||
|
this.themeManager = null;
|
||||||
|
this.deviceManager = null;
|
||||||
|
this.uiRenderer = null;
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.init = function(userConfig) {
|
||||||
|
if (this.initialized) {
|
||||||
|
console.warn('Fido2UIManager already initialized');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.jQuery) {
|
||||||
|
throw new Error('jQuery is required. Please include jQuery before fido2-ui-sdk.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.bootstrap) {
|
||||||
|
throw new Error('Bootstrap is required. Please include Bootstrap JS before fido2-ui-sdk.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config = Object.assign({}, DEFAULT_CONFIG, userConfig);
|
||||||
|
this.i18n = new I18nManager(this.config);
|
||||||
|
this.eventManager = new EventManager();
|
||||||
|
this.themeManager = new ThemeManager(this.config);
|
||||||
|
this.deviceManager = new DeviceManager(this.config, this.i18n, this.eventManager);
|
||||||
|
this.uiRenderer = new UIRenderer(this.config, this.i18n, this.themeManager, this.eventManager);
|
||||||
|
|
||||||
|
if (this.config.serverUrl) {
|
||||||
|
setFidoServerURL(this.config.serverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setupCallbacks();
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype._setupCallbacks = function() {
|
||||||
|
const callbacks = this.config.callbacks;
|
||||||
|
|
||||||
|
if (callbacks.onInit) {
|
||||||
|
this.eventManager.on('init', callbacks.onInit);
|
||||||
|
}
|
||||||
|
if (callbacks.onDeviceAdded) {
|
||||||
|
this.eventManager.on('deviceAdded', callbacks.onDeviceAdded);
|
||||||
|
}
|
||||||
|
if (callbacks.onDeviceDeleted) {
|
||||||
|
this.eventManager.on('deviceDeleted', callbacks.onDeviceDeleted);
|
||||||
|
}
|
||||||
|
if (callbacks.onDeviceListLoaded) {
|
||||||
|
this.eventManager.on('deviceListLoaded', callbacks.onDeviceListLoaded);
|
||||||
|
}
|
||||||
|
if (callbacks.onError) {
|
||||||
|
this.eventManager.on('error', callbacks.onError);
|
||||||
|
}
|
||||||
|
if (callbacks.onClose) {
|
||||||
|
this.eventManager.on('close', callbacks.onClose);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.renderDeviceManager = function(config) {
|
||||||
|
const manager = new Fido2UIManager();
|
||||||
|
manager.init(config);
|
||||||
|
|
||||||
|
const mode = manager.config.mode;
|
||||||
|
|
||||||
|
if (mode === 'standalone') {
|
||||||
|
manager.uiRenderer.renderStandalone();
|
||||||
|
} else {
|
||||||
|
manager.uiRenderer.renderModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
manager._bindInternalEvents();
|
||||||
|
manager._loadInitialData();
|
||||||
|
manager.deviceManager.startAutoRefresh();
|
||||||
|
|
||||||
|
manager.eventManager.emit('init', manager);
|
||||||
|
|
||||||
|
window.Fido2UIManager = manager;
|
||||||
|
|
||||||
|
return manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype._bindInternalEvents = function() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
this.eventManager.on('addDevice', async () => {
|
||||||
|
const addBtn = document.getElementById('fido2AddDeviceBtn');
|
||||||
|
if (addBtn) addBtn.disabled = true;
|
||||||
|
try {
|
||||||
|
const isValid = await self.deviceManager.checkSession();
|
||||||
|
if (!isValid) {
|
||||||
|
alert(self.i18n.getText('msg_session_status_fail') + ' ' + self.i18n.getText('btn_login'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await self.deviceManager.addDevice();
|
||||||
|
self.uiRenderer.updateDevicesList(self.deviceManager.devices);
|
||||||
|
await self.deviceManager.checkSession();
|
||||||
|
alert(self.i18n.getText('msg_register_ok'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Add device error:', error);
|
||||||
|
alert(error.message || self.i18n.getText('error'));
|
||||||
|
} finally {
|
||||||
|
if (addBtn) addBtn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('deleteDevice', async (deviceId) => {
|
||||||
|
try {
|
||||||
|
const isValid = await self.deviceManager.checkSession();
|
||||||
|
if (!isValid) {
|
||||||
|
alert(self.i18n.getText('msg_session_status_fail') + ' ' + self.i18n.getText('btn_login'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await self.deviceManager.deleteDevice(deviceId);
|
||||||
|
self.uiRenderer.updateDevicesList(self.deviceManager.devices);
|
||||||
|
await self.deviceManager.checkSession();
|
||||||
|
alert(self.i18n.getText('msg_deldev_ok'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Delete device error:', error);
|
||||||
|
alert(error.message || self.i18n.getText('error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('login', async () => {
|
||||||
|
try {
|
||||||
|
const result = await authenticateFido2(null, self.config.rpId);
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
await self.deviceManager.loadDevices();
|
||||||
|
await self.deviceManager.checkSession();
|
||||||
|
self.uiRenderer.updateDevicesList(self.deviceManager.devices);
|
||||||
|
} else {
|
||||||
|
alert(result.errorMessage || 'Login failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
alert(error.message || 'Login failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('deviceListLoaded', (devices) => {
|
||||||
|
self.uiRenderer.updateDevicesList(devices);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('sessionStatusChanged', (isValid) => {
|
||||||
|
self.uiRenderer.updateSessionStatus(isValid);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('error', (error) => {
|
||||||
|
console.error('FIDO2 SDK Error:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype._loadInitialData = async function() {
|
||||||
|
try {
|
||||||
|
await this.deviceManager.loadDevices();
|
||||||
|
await this.deviceManager.checkSession();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Initial data load error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.close = function() {
|
||||||
|
if (this.uiRenderer) {
|
||||||
|
this.uiRenderer.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.refresh = async function() {
|
||||||
|
if (this.deviceManager) {
|
||||||
|
await this.deviceManager.loadDevices();
|
||||||
|
await this.deviceManager.checkSession();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.destroy = function() {
|
||||||
|
if (this.deviceManager) {
|
||||||
|
this.deviceManager.stopAutoRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.uiRenderer) {
|
||||||
|
this.uiRenderer.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Fido2UIManager = new Fido2UIManager();
|
||||||
|
window.Fido2UIManager.renderDeviceManager = Fido2UIManager.prototype.renderDeviceManager;
|
||||||
|
window.Fido2UIManager.close = function() {
|
||||||
|
if (window.Fido2UIManager && window.Fido2UIManager.close) {
|
||||||
|
window.Fido2UIManager.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.Fido2UIManager.refresh = function() {
|
||||||
|
if (window.Fido2UIManager && window.Fido2UIManager.refresh) {
|
||||||
|
window.Fido2UIManager.refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.Fido2UIManager.destroy = function() {
|
||||||
|
if (window.Fido2UIManager && window.Fido2UIManager.destroy) {
|
||||||
|
window.Fido2UIManager.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('FIDO2 UI SDK v' + FIDO2_UI_VERSION + ' loaded');
|
||||||
|
})(window);
|
||||||
51
login.html
51
login.html
@@ -62,7 +62,7 @@
|
|||||||
// For stand alone: 'https://local.dqj-macpro.com'
|
// For stand alone: 'https://local.dqj-macpro.com'
|
||||||
// For proxy: set 'https://mac-air-m2.dqj-home.com'
|
// For proxy: set 'https://mac-air-m2.dqj-home.com'
|
||||||
-->
|
-->
|
||||||
setFidoServerURL('https://mac-air-m2.dqj-home.com');//'https://fido2.amipro.me');
|
setFidoServerURL('https://local.dqj-macpro.com');//'https://fido2.amipro.me');
|
||||||
|
|
||||||
const i18n_messages = new Map();
|
const i18n_messages = new Map();
|
||||||
|
|
||||||
@@ -114,6 +114,42 @@
|
|||||||
lang_map.set("ja", "パスワードレス ログイン");
|
lang_map.set("ja", "パスワードレス ログイン");
|
||||||
i18n_messages.set("title_fido2_login", lang_map);
|
i18n_messages.set("title_fido2_login", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "This sample is production-oriented.");
|
||||||
|
lang_map.set("zh-CN", "此示例面向生产环境。");
|
||||||
|
lang_map.set("ja", "このサンプルは本番指向です。");
|
||||||
|
i18n_messages.set("msg_prod_note_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Handles real-world browser differences");
|
||||||
|
lang_map.set("zh-CN", "处理真实世界的浏览器差异");
|
||||||
|
lang_map.set("ja", "実際のブラウザー差異に対応");
|
||||||
|
i18n_messages.set("msg_prod_note_1", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Uses recommended WebAuthn options");
|
||||||
|
lang_map.set("zh-CN", "使用推荐的 WebAuthn 选项");
|
||||||
|
lang_map.set("ja", "推奨される WebAuthn オプションを使用");
|
||||||
|
i18n_messages.set("msg_prod_note_2", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Mirrors production flow (RP ID, challenge, verification)");
|
||||||
|
lang_map.set("zh-CN", "模拟生产流程(RP ID、质询、验证)");
|
||||||
|
lang_map.set("ja", "本番フロー(RP ID、チャレンジ、検証)を再現");
|
||||||
|
i18n_messages.set("msg_prod_note_3", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Safe to use as a starting point");
|
||||||
|
lang_map.set("zh-CN", "可安全作为起点使用");
|
||||||
|
lang_map.set("ja", "スターターとして安全に利用可能");
|
||||||
|
i18n_messages.set("msg_prod_note_4", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "15-minute integration guide");
|
||||||
|
lang_map.set("zh-CN", "15分钟接入指南");
|
||||||
|
lang_map.set("ja", "15分での導入ガイド");
|
||||||
|
i18n_messages.set("msg_integration_link", lang_map);
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
logoutFido2UserSession();
|
logoutFido2UserSession();
|
||||||
|
|
||||||
@@ -173,6 +209,19 @@
|
|||||||
<h4 class="mb-2" id="msg_welcome">Welcome to amiPro sample site!</h4>
|
<h4 class="mb-2" id="msg_welcome">Welcome to amiPro sample site!</h4>
|
||||||
<p class="mb-4" id="msg_intro">Please sign-in to your account and start the adventure</p>
|
<p class="mb-4" id="msg_intro">Please sign-in to your account and start the adventure</p>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="fw-semibold mb-2" id="msg_prod_note_title">This sample is production-oriented.</p>
|
||||||
|
<ul class="ps-3 mb-4" style="list-style: disc;">
|
||||||
|
<li id="msg_prod_note_1">Handles real-world browser differences</li>
|
||||||
|
<li id="msg_prod_note_2">Uses recommended WebAuthn options</li>
|
||||||
|
<li id="msg_prod_note_3">Mirrors production flow (RP ID, challenge, verification)</li>
|
||||||
|
<li id="msg_prod_note_4">Safe to use as a starting point</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mb-4">
|
||||||
|
<a id="msg_integration_link" href="https://www.amipro.me/integration.html" target="_blank" rel="noopener">30-minute integration guide</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="msg_uid_input" style="display:none;">Input User ID, please!</div>
|
<div id="msg_uid_input" style="display:none;">Input User ID, please!</div>
|
||||||
|
|
||||||
<form class="mb-3" action="devices.html" method="GET" onsubmit="return checkInput();">
|
<form class="mb-3" action="devices.html" method="GET" onsubmit="return checkInput();">
|
||||||
|
|||||||
541
modal-demo.html
Normal file
541
modal-demo.html
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FIDO2 UI SDK - Modal Demo</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="files/core.css">
|
||||||
|
<link rel="stylesheet" href="files/theme-default.css">
|
||||||
|
<link rel="stylesheet" href="files/demo.css">
|
||||||
|
<link rel="stylesheet" href="files/boxicons.css">
|
||||||
|
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-header {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-header h1 {
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-header p {
|
||||||
|
color: #666;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section h2 {
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section p {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn {
|
||||||
|
padding: 12px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-primary {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-secondary {
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-secondary:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block code {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list li {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list i {
|
||||||
|
color: #28a745;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log-container {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry {
|
||||||
|
padding: 4px 0;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-time {
|
||||||
|
color: #569cd6;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-type-info {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-type-success {
|
||||||
|
color: #6a9955;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-type-error {
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="files/jquery.js"></script>
|
||||||
|
<script src="files/popper.js"></script>
|
||||||
|
<script src="files/bootstrap.js"></script>
|
||||||
|
<script src="files/dfido2-lib.js"></script>
|
||||||
|
<script src="files/fido2-ui-sdk.js"></script>
|
||||||
|
<script src="files/amipro_utils.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const i18n_messages = new Map();
|
||||||
|
|
||||||
|
var lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "🔐 FIDO2 UI SDK - Modal Demo");
|
||||||
|
lang_map.set("zh-CN", "🔐 FIDO2 UI SDK - 模态框演示");
|
||||||
|
lang_map.set("ja", "🔐 FIDO2 UI SDK - モーダル デモ");
|
||||||
|
i18n_messages.set("msg_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Demonstrate how to use FIDO2 UI SDK to manage devices in a popup window");
|
||||||
|
lang_map.set("zh-CN", "演示如何使用 FIDO2 UI SDK 在弹出窗口中管理设备");
|
||||||
|
lang_map.set("ja", "FIDO2 UI SDK を使用してポップアップ ウィンドウでデバイスを管理する方法をデモします");
|
||||||
|
i18n_messages.set("msg_subtitle", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "📌 Quick Start");
|
||||||
|
lang_map.set("zh-CN", "📌 快速开始");
|
||||||
|
lang_map.set("ja", "📌 クイック スタート");
|
||||||
|
i18n_messages.set("msg_section_quick_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Click the button below to open the device manager modal:");
|
||||||
|
lang_map.set("zh-CN", "点击下面的按钮打开设备管理模态框:");
|
||||||
|
lang_map.set("ja", "以下のボタンを押してデバイス管理モーダルを開いてください:");
|
||||||
|
i18n_messages.set("msg_quick_desc", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Manage Devices");
|
||||||
|
lang_map.set("zh-CN", "管理设备");
|
||||||
|
lang_map.set("ja", "デバイス管理");
|
||||||
|
i18n_messages.set("msg_btn_manage", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Custom Style");
|
||||||
|
lang_map.set("zh-CN", "自定义样式");
|
||||||
|
lang_map.set("ja", "カスタム スタイル");
|
||||||
|
i18n_messages.set("msg_btn_custom", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "💻 Code Examples");
|
||||||
|
lang_map.set("zh-CN", "💻 代码示例");
|
||||||
|
lang_map.set("ja", "💻 コード例");
|
||||||
|
i18n_messages.set("msg_section_code_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Simplest integration:");
|
||||||
|
lang_map.set("zh-CN", "最简单的集成方式:");
|
||||||
|
lang_map.set("ja", "最も簡単な統合:");
|
||||||
|
i18n_messages.set("msg_code_simple", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "With theme customization:");
|
||||||
|
lang_map.set("zh-CN", "带主题定制:");
|
||||||
|
lang_map.set("ja", "テーマのカスタマイズあり:");
|
||||||
|
i18n_messages.set("msg_code_theme", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "✨ Features");
|
||||||
|
lang_map.set("zh-CN", "✨ 功能特性");
|
||||||
|
lang_map.set("ja", "✨ 機能");
|
||||||
|
i18n_messages.set("msg_section_features_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Popup mode, stay on current page");
|
||||||
|
lang_map.set("zh-CN", "弹出窗口模式,不离开当前页面");
|
||||||
|
lang_map.set("ja", "ポップアップ モードで現在のページに留まる");
|
||||||
|
i18n_messages.set("msg_feature_1", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Add/delete FIDO2 devices support");
|
||||||
|
lang_map.set("zh-CN", "支持添加/删除 FIDO2 设备");
|
||||||
|
lang_map.set("ja", "FIDO2 デバイスの追加/削除サポート");
|
||||||
|
i18n_messages.set("msg_feature_2", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Real-time device list display");
|
||||||
|
lang_map.set("zh-CN", "实时显示设备列表");
|
||||||
|
lang_map.set("ja", "リアルタイム デバイスリスト表示");
|
||||||
|
i18n_messages.set("msg_feature_3", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Session status monitoring");
|
||||||
|
lang_map.set("zh-CN", "会话状态监控");
|
||||||
|
lang_map.set("ja", "セッション ステータス監視");
|
||||||
|
i18n_messages.set("msg_feature_4", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Theme color and style customization");
|
||||||
|
lang_map.set("zh-CN", "主题色和样式定制");
|
||||||
|
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
|
||||||
|
i18n_messages.set("msg_feature_5", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Multi-language support (Chinese/English/Japanese)");
|
||||||
|
lang_map.set("zh-CN", "多语言支持(中/英/日)");
|
||||||
|
lang_map.set("ja", "多言語対応(中/英/日)");
|
||||||
|
i18n_messages.set("msg_feature_6", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Event callback system");
|
||||||
|
lang_map.set("zh-CN", "事件回调系统");
|
||||||
|
lang_map.set("ja", "イベント コールバック システム");
|
||||||
|
i18n_messages.set("msg_feature_7", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "📊 Event Log");
|
||||||
|
lang_map.set("zh-CN", "📊 事件日志");
|
||||||
|
lang_map.set("ja", "📊 イベント ログ");
|
||||||
|
i18n_messages.set("msg_section_log_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Clear Log");
|
||||||
|
lang_map.set("zh-CN", "清空日志");
|
||||||
|
lang_map.set("ja", "ログをクリア");
|
||||||
|
i18n_messages.set("msg_btn_clear", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Opening default device manager...");
|
||||||
|
lang_map.set("zh-CN", "打开默认设备管理器...");
|
||||||
|
lang_map.set("ja", "デフォルト デバイス マネージャーを開いています...");
|
||||||
|
i18n_messages.set("msg_log_open_default", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Opening custom style device manager...");
|
||||||
|
lang_map.set("zh-CN", "打开自定义样式设备管理器...");
|
||||||
|
lang_map.set("ja", "カスタム スタイル デバイス マネージャーを開いています...");
|
||||||
|
i18n_messages.set("msg_log_open_custom", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Device manager initialized successfully");
|
||||||
|
lang_map.set("zh-CN", "设备管理器初始化完成");
|
||||||
|
lang_map.set("ja", "デバイス マネージャーの初期化が完了しました");
|
||||||
|
i18n_messages.set("msg_log_init", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Custom style device manager initialized successfully");
|
||||||
|
lang_map.set("zh-CN", "自定义样式设备管理器初始化完成");
|
||||||
|
lang_map.set("ja", "カスタム スタイル デバイス マネージャーの初期化が完了しました");
|
||||||
|
i18n_messages.set("msg_log_init_custom", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Device added successfully");
|
||||||
|
lang_map.set("zh-CN", "设备添加成功");
|
||||||
|
lang_map.set("ja", "デバイスの追加が完了しました");
|
||||||
|
i18n_messages.set("msg_log_device_added", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Device deleted successfully");
|
||||||
|
lang_map.set("zh-CN", "设备删除成功");
|
||||||
|
lang_map.set("ja", "デバイスの削除が完了しました");
|
||||||
|
i18n_messages.set("msg_log_device_deleted", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Device list loaded, total");
|
||||||
|
lang_map.set("zh-CN", "设备列表加载完成,共");
|
||||||
|
lang_map.set("ja", "デバイスリストが読み込まれました。合計");
|
||||||
|
i18n_messages.set("msg_log_devices_loaded", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "devices");
|
||||||
|
lang_map.set("zh-CN", "个设备");
|
||||||
|
lang_map.set("ja", "デバイス");
|
||||||
|
i18n_messages.set("msg_log_devices_suffix", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "An error occurred");
|
||||||
|
lang_map.set("zh-CN", "发生错误");
|
||||||
|
lang_map.set("ja", "エラーが発生しました");
|
||||||
|
i18n_messages.set("msg_log_error", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Device manager closed");
|
||||||
|
lang_map.set("zh-CN", "设备管理器已关闭");
|
||||||
|
lang_map.set("ja", "デバイス マネージャーが閉じられました");
|
||||||
|
i18n_messages.set("msg_log_closed", lang_map);
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
setI18NText(i18n_messages);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="demo-header">
|
||||||
|
<h1 id="msg_title">🔐 FIDO2 UI SDK - Modal Demo</h1>
|
||||||
|
<p id="msg_subtitle">Demonstrate how to use FIDO2 UI SDK to manage devices in a popup window</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_quick_title">📌 Quick Start</h2>
|
||||||
|
<p id="msg_quick_desc">Click the button below to open the device manager modal:</p>
|
||||||
|
<button class="demo-btn demo-btn-primary" onclick="openDeviceManager()">
|
||||||
|
<i class="bx bx-device"></i> <span id="msg_btn_manage">Manage Devices</span>
|
||||||
|
</button>
|
||||||
|
<button class="demo-btn demo-btn-secondary" onclick="openCustomDeviceManager()">
|
||||||
|
<i class="bx bx-palette"></i> <span id="msg_btn_custom">Custom Style</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_code_title">💻 Code Examples</h2>
|
||||||
|
<p id="msg_code_simple">Simplest integration:</p>
|
||||||
|
<div class="code-block">
|
||||||
|
<code>Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: SERVER_URL
|
||||||
|
});</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="msg_code_theme">With theme customization:</p>
|
||||||
|
<div class="code-block">
|
||||||
|
<code>Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
theme: {
|
||||||
|
primaryColor: '#ce59d9',
|
||||||
|
backgroundColor: '#f8f9fa'
|
||||||
|
},
|
||||||
|
language: 'zh-CN'
|
||||||
|
});</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_features_title">✨ Features</h2>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_1">Popup mode, stay on current page</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_2">Add/delete FIDO2 devices support</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_3">Real-time device list display</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_4">Session status monitoring</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_5">Theme color and style customization</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_6">Multi-language support (Chinese/English/Japanese)</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_7">Event callback system</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_log_title">📊 Event Log</h2>
|
||||||
|
<div id="log-container"></div>
|
||||||
|
<button class="demo-btn demo-btn-secondary" onclick="clearLog()" style="margin-top: 15px;">
|
||||||
|
<span id="msg_btn_clear">Clear Log</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="device-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const SERVER_URL = 'https://local.dqj-macpro.com';
|
||||||
|
|
||||||
|
function getBrowserLanguage() {
|
||||||
|
const lang = window.navigator.language || window.navigator.userLanguage;
|
||||||
|
const supportedLangs = ['en-US', 'zh-CN', 'ja'];
|
||||||
|
if (supportedLangs.includes(lang)) {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
return 'en-US';
|
||||||
|
}
|
||||||
|
|
||||||
|
const CURRENT_LANG = getBrowserLanguage();//'https://fido2.amipro.me';
|
||||||
|
|
||||||
|
function openDeviceManager() {
|
||||||
|
clearLog();
|
||||||
|
log('info', getI18NText(i18n_messages, 'msg_log_open_default'));
|
||||||
|
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
language: CURRENT_LANG,
|
||||||
|
callbacks: {
|
||||||
|
onInit: function(manager) {
|
||||||
|
log('success', getI18NText(i18n_messages, 'msg_log_init'));
|
||||||
|
},
|
||||||
|
onDeviceAdded: function(device) {
|
||||||
|
log('success', getI18NText(i18n_messages, 'msg_log_device_added') + ': ' + JSON.stringify(device));
|
||||||
|
},
|
||||||
|
onDeviceDeleted: function(deviceId) {
|
||||||
|
log('success', getI18NText(i18n_messages, 'msg_log_device_deleted') + ': ' + deviceId);
|
||||||
|
},
|
||||||
|
onDeviceListLoaded: function(devices) {
|
||||||
|
log('info', getI18NText(i18n_messages, 'msg_log_devices_loaded') + ' ' + devices.length + ' ' + getI18NText(i18n_messages, 'msg_log_devices_suffix'));
|
||||||
|
},
|
||||||
|
onError: function(error) {
|
||||||
|
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + error.message);
|
||||||
|
},
|
||||||
|
onClose: function() {
|
||||||
|
log('info', getI18NText(i18n_messages, 'msg_log_closed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCustomDeviceManager() {
|
||||||
|
clearLog();
|
||||||
|
log('info', getI18NText(i18n_messages, 'msg_log_open_custom'));
|
||||||
|
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
container: '#device-container',
|
||||||
|
mode: 'modal',
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
language: CURRENT_LANG,
|
||||||
|
theme: {
|
||||||
|
logo: 'files/favicon.ico',
|
||||||
|
primaryColor: '#ce59d9',
|
||||||
|
backgroundColor: '#faf5ff',
|
||||||
|
textColor: '#6b21a8',
|
||||||
|
borderRadius: '12px'
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
showAddButton: true,
|
||||||
|
showDeleteButton: true,
|
||||||
|
showUserInfo: true,
|
||||||
|
showSessionStatus: true
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
onInit: function(manager) {
|
||||||
|
log('success', getI18NText(i18n_messages, 'msg_log_init_custom'));
|
||||||
|
},
|
||||||
|
onDeviceAdded: function(device) {
|
||||||
|
log('success', getI18NText(i18n_messages, 'msg_log_device_added') + ': ' + JSON.stringify(device));
|
||||||
|
},
|
||||||
|
onDeviceDeleted: function(deviceId) {
|
||||||
|
log('success', getI18NText(i18n_messages, 'msg_log_device_deleted') + ': ' + deviceId);
|
||||||
|
},
|
||||||
|
onDeviceListLoaded: function(devices) {
|
||||||
|
log('info', getI18NText(i18n_messages, 'msg_log_devices_loaded') + ' ' + devices.length + ' ' + getI18NText(i18n_messages, 'msg_log_devices_suffix'));
|
||||||
|
},
|
||||||
|
onError: function(error) {
|
||||||
|
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + error.message);
|
||||||
|
},
|
||||||
|
onClose: function() {
|
||||||
|
log('info', getI18NText(i18n_messages, 'msg_log_closed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(type, message) {
|
||||||
|
const container = document.getElementById('log-container');
|
||||||
|
const entry = document.createElement('div');
|
||||||
|
entry.className = 'log-entry';
|
||||||
|
|
||||||
|
const time = new Date().toLocaleTimeString('zh-CN');
|
||||||
|
const typeClass = 'log-type-' + type;
|
||||||
|
|
||||||
|
entry.innerHTML = `<span class="log-time">[${time}]</span><span class="${typeClass}">[${type.toUpperCase()}]</span> ${message}`;
|
||||||
|
container.appendChild(entry);
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLog() {
|
||||||
|
document.getElementById('log-container').innerHTML = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
534
standalone-demo.html
Normal file
534
standalone-demo.html
Normal file
@@ -0,0 +1,534 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FIDO2 UI SDK - Standalone Demo</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="files/core.css">
|
||||||
|
<link rel="stylesheet" href="files/theme-default.css">
|
||||||
|
<link rel="stylesheet" href="files/demo.css">
|
||||||
|
<link rel="stylesheet" href="files/boxicons.css">
|
||||||
|
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-header {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-header h1 {
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-header p {
|
||||||
|
color: #666;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section h2 {
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section p {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn {
|
||||||
|
padding: 12px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-primary {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(240, 147, 251, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-secondary {
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-btn-secondary:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block code {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list li {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list i {
|
||||||
|
color: #28a745;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #e7f3ff;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box strong {
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="files/jquery.js"></script>
|
||||||
|
<script src="files/popper.js"></script>
|
||||||
|
<script src="files/bootstrap.js"></script>
|
||||||
|
<script src="files/dfido2-lib.js"></script>
|
||||||
|
<script src="files/fido2-ui-sdk.js"></script>
|
||||||
|
<script src="files/amipro_utils.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const i18n_messages = new Map();
|
||||||
|
|
||||||
|
var lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "🔐 FIDO2 UI SDK - Standalone Demo");
|
||||||
|
lang_map.set("zh-CN", "🔐 FIDO2 UI SDK - 独立页面演示");
|
||||||
|
lang_map.set("ja", "🔐 FIDO2 UI SDK - スタンドアロン デモ");
|
||||||
|
i18n_messages.set("msg_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Demonstrate how to use FIDO2 UI SDK to manage devices in a standalone page");
|
||||||
|
lang_map.set("zh-CN", "演示如何使用 FIDO2 UI SDK 在独立页面中管理设备");
|
||||||
|
lang_map.set("ja", "FIDO2 UI SDK を使用してスタンドアロン ページでデバイスを管理する方法をデモします");
|
||||||
|
i18n_messages.set("msg_subtitle", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "📌 What is Standalone Page Mode?");
|
||||||
|
lang_map.set("zh-CN", "📌 什么是独立页面模式?");
|
||||||
|
lang_map.set("ja", "📌 スタンドアロン ページ モードとは?");
|
||||||
|
i18n_messages.set("msg_section_mode_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Standalone page mode renders a complete device management interface on the current page, suitable for scenarios requiring a dedicated device management page.");
|
||||||
|
lang_map.set("zh-CN", "独立页面模式会在当前页面渲染完整的设备管理界面,适用于需要独立设备管理页面的场景。");
|
||||||
|
lang_map.set("ja", "スタンドアロン ページ モードは、現在のページに完全なデバイス管理インターフェイスをレンダリングし、専用のデバイス管理ページが必要なシナリオに適しています。");
|
||||||
|
i18n_messages.set("msg_section_mode_desc", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "⚠️ Note:");
|
||||||
|
lang_map.set("zh-CN", "⚠️ 注意:");
|
||||||
|
lang_map.set("ja", "⚠️ 注意:");
|
||||||
|
i18n_messages.set("msg_note", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Standalone page mode will replace the current page content. If your page already has a complete layout (like login.html or devices.html), it is recommended to use modal mode.");
|
||||||
|
lang_map.set("zh-CN", "独立页面模式会替换当前页面的内容。如果您的页面已经加载了完整的布局(例如 login.html 或 devices.html),建议使用模态框模式。");
|
||||||
|
lang_map.set("ja", "スタンドアロン ページ モードは現在のページ内容を置き換えます。ページにすでに完全なレイアウト(login.html や devices.html など)が読み込まれている場合は、モーダル モードの使用をお勧めします。");
|
||||||
|
i18n_messages.set("msg_note_content", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "💻 Code Examples");
|
||||||
|
lang_map.set("zh-CN", "💻 代码示例");
|
||||||
|
lang_map.set("ja", "💻 コード例");
|
||||||
|
i18n_messages.set("msg_section_code_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Simplest standalone integration:");
|
||||||
|
lang_map.set("zh-CN", "最简单的独立页面集成方式:");
|
||||||
|
lang_map.set("ja", "最も簡単なスタンドアロン統合:");
|
||||||
|
i18n_messages.set("msg_code_simple", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "With theme customization:");
|
||||||
|
lang_map.set("zh-CN", "带主题定制:");
|
||||||
|
lang_map.set("ja", "テーマのカスタマイズあり:");
|
||||||
|
i18n_messages.set("msg_code_theme", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "✨ Features");
|
||||||
|
lang_map.set("zh-CN", "✨ 功能特性");
|
||||||
|
lang_map.set("ja", "✨ 機能");
|
||||||
|
i18n_messages.set("msg_section_features_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Standalone page with complete device management interface");
|
||||||
|
lang_map.set("zh-CN", "独立页面,完整的设备管理界面");
|
||||||
|
lang_map.set("ja", "スタンドアロン ページで完全なデバイス管理インターフェイス");
|
||||||
|
i18n_messages.set("msg_feature_1", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Add/delete FIDO2 devices support");
|
||||||
|
lang_map.set("zh-CN", "支持添加/删除 FIDO2 设备");
|
||||||
|
lang_map.set("ja", "FIDO2 デバイスの追加/削除サポート");
|
||||||
|
i18n_messages.set("msg_feature_2", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Real-time device list display");
|
||||||
|
lang_map.set("zh-CN", "实时显示设备列表");
|
||||||
|
lang_map.set("ja", "リアルタイム デバイスリスト表示");
|
||||||
|
i18n_messages.set("msg_feature_3", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Session status monitoring");
|
||||||
|
lang_map.set("zh-CN", "会话状态监控");
|
||||||
|
lang_map.set("ja", "セッション ステータス監視");
|
||||||
|
i18n_messages.set("msg_feature_4", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Theme color and style customization");
|
||||||
|
lang_map.set("zh-CN", "主题色和样式定制");
|
||||||
|
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
|
||||||
|
i18n_messages.set("msg_feature_5", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Multi-language support (Chinese/English/Japanese)");
|
||||||
|
lang_map.set("zh-CN", "多语言支持(中/英/日)");
|
||||||
|
lang_map.set("ja", "多言語対応(中/英/日)");
|
||||||
|
i18n_messages.set("msg_feature_6", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Event callback system");
|
||||||
|
lang_map.set("zh-CN", "事件回调系统");
|
||||||
|
lang_map.set("ja", "イベント コールバック システム");
|
||||||
|
i18n_messages.set("msg_feature_7", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Responsive design, mobile support");
|
||||||
|
lang_map.set("zh-CN", "响应式设计,支持移动端");
|
||||||
|
lang_map.set("ja", "レスポンシブ デザイン、モバイル対応");
|
||||||
|
i18n_messages.set("msg_feature_8", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "🚀 Demo");
|
||||||
|
lang_map.set("zh-CN", "🚀 演示");
|
||||||
|
lang_map.set("ja", "🚀 デモ");
|
||||||
|
i18n_messages.set("msg_section_demo_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Click the buttons below to experience standalone page mode:");
|
||||||
|
lang_map.set("zh-CN", "点击下面的按钮体验独立页面模式:");
|
||||||
|
lang_map.set("ja", "以下のボタンを押してスタンドアロン ページ モードを体験してください:");
|
||||||
|
i18n_messages.set("msg_demo_desc", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Default Style");
|
||||||
|
lang_map.set("zh-CN", "默认样式");
|
||||||
|
lang_map.set("ja", "デフォルト スタイル");
|
||||||
|
i18n_messages.set("msg_btn_default", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Custom Style");
|
||||||
|
lang_map.set("zh-CN", "自定义样式");
|
||||||
|
lang_map.set("ja", "カスタム スタイル");
|
||||||
|
i18n_messages.set("msg_btn_custom", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "💡 Tip:");
|
||||||
|
lang_map.set("zh-CN", "💡 提示:");
|
||||||
|
lang_map.set("ja", "💡 ヒント:");
|
||||||
|
i18n_messages.set("msg_tip", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "After clicking the button, the page will be replaced with the device management interface. To return to this demo page, please refresh the browser.");
|
||||||
|
lang_map.set("zh-CN", "点击按钮后,页面将被替换为设备管理界面。要返回此演示页面,请刷新浏览器。");
|
||||||
|
lang_map.set("ja", "ボタンを押すと、ページはデバイス管理インターフェイスに置き換えられます。このデモ ページに戻るには、ブラウザを更新してください。");
|
||||||
|
i18n_messages.set("msg_tip_content", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "📖 Use Cases");
|
||||||
|
lang_map.set("zh-CN", "📖 使用场景");
|
||||||
|
lang_map.set("ja", "📖 ユースケース");
|
||||||
|
i18n_messages.set("msg_section_usecases_title", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Standalone device management page (similar to existing devices.html)");
|
||||||
|
lang_map.set("zh-CN", "需要独立的设备管理页面(类似现有的 devices.html)");
|
||||||
|
lang_map.set("ja", "専用のデバイス管理ページ(既存の devices.html と同様)");
|
||||||
|
i18n_messages.set("msg_usecase_1", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "User accesses device management directly via link");
|
||||||
|
lang_map.set("zh-CN", "用户需要通过链接直接访问设备管理");
|
||||||
|
lang_map.set("ja", "ユーザーがリンクからデバイス管理に直接アクセス");
|
||||||
|
i18n_messages.set("msg_usecase_2", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Device management as a standalone page feature");
|
||||||
|
lang_map.set("zh-CN", "设备管理作为独立的页面功能");
|
||||||
|
lang_map.set("ja", "デバイス管理がスタンドアロン ページ機能として");
|
||||||
|
i18n_messages.set("msg_usecase_3", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Need more screen space to display device information");
|
||||||
|
lang_map.set("zh-CN", "需要更多屏幕空间显示设备信息");
|
||||||
|
lang_map.set("ja", "デバイス情報を表示するためにより多くの画面スペースが必要");
|
||||||
|
i18n_messages.set("msg_usecase_4", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Return to Device Management Page");
|
||||||
|
lang_map.set("zh-CN", "返回设备管理页面");
|
||||||
|
lang_map.set("ja", "デバイス管理ページに戻る");
|
||||||
|
i18n_messages.set("msg_btn_back", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "This operation will replace the current page content. Are you sure you want to continue?");
|
||||||
|
lang_map.set("zh-CN", "此操作将替换当前页面的内容。确定要继续吗?");
|
||||||
|
lang_map.set("ja", "この操作は現在のページ内容を置き換えます。よろしいですか?");
|
||||||
|
i18n_messages.set("msg_confirm", lang_map);
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
setI18NText(i18n_messages);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="demo-header">
|
||||||
|
<h1 id="msg_title">🔐 FIDO2 UI SDK - Standalone Demo</h1>
|
||||||
|
<p id="msg_subtitle">Demonstrate how to use FIDO2 UI SDK to manage devices in a standalone page</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_mode_title">📌 What is Standalone Page Mode?</h2>
|
||||||
|
<p id="msg_section_mode_desc">Standalone page mode renders a complete device management interface on the current page, suitable for scenarios requiring a dedicated device management page.</p>
|
||||||
|
<div class="info-box">
|
||||||
|
<strong id="msg_note">⚠️ Note:</strong>
|
||||||
|
<p id="msg_note_content">Standalone page mode will replace the current page content. If your page already has a complete layout (like login.html or devices.html), it is recommended to use modal mode.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_code_title">💻 Code Examples</h2>
|
||||||
|
<p id="msg_code_simple">Simplest standalone integration:</p>
|
||||||
|
<div class="code-block">
|
||||||
|
<code>Fido2UIManager.renderDeviceManager({
|
||||||
|
mode: 'standalone',
|
||||||
|
serverUrl: SERVER_URL
|
||||||
|
});</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="msg_code_theme">With theme customization:</p>
|
||||||
|
<div class="code-block">
|
||||||
|
<code>Fido2UIManager.renderDeviceManager({
|
||||||
|
mode: 'standalone',
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
theme: {
|
||||||
|
logo: 'path/to/logo.png',
|
||||||
|
primaryColor: '#f5576c',
|
||||||
|
backgroundColor: '#ffffff'
|
||||||
|
},
|
||||||
|
language: 'zh-CN'
|
||||||
|
});</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_features_title">✨ Features</h2>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_1">Standalone page with complete device management interface</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_2">Add/delete FIDO2 devices support</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_3">Real-time device list display</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_4">Session status monitoring</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_5">Theme color and style customization</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_6">Multi-language support (Chinese/English/Japanese)</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_7">Event callback system</span></li>
|
||||||
|
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_8">Responsive design, mobile support</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_demo_title">🚀 Demo</h2>
|
||||||
|
<p id="msg_demo_desc">Click the buttons below to experience standalone page mode:</p>
|
||||||
|
<button class="demo-btn demo-btn-primary" onclick="openStandaloneDefault()">
|
||||||
|
<i class="bx bx-desktop"></i> <span id="msg_btn_default">Default Style</span>
|
||||||
|
</button>
|
||||||
|
<button class="demo-btn demo-btn-secondary" onclick="openStandaloneCustom()">
|
||||||
|
<i class="bx bx-palette"></i> <span id="msg_btn_custom">Custom Style</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="info-box" style="margin-top: 20px;">
|
||||||
|
<strong id="msg_tip">💡 Tip:</strong>
|
||||||
|
<p id="msg_tip_content">After clicking the button, the page will be replaced with the device management interface. To return to this demo page, please refresh the browser.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-section">
|
||||||
|
<h2 id="msg_section_usecases_title">📖 Use Cases</h2>
|
||||||
|
<p>Standalone page mode is suitable for the following scenarios:</p>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_1">Standalone device management page (similar to existing devices.html)</span></li>
|
||||||
|
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_2">User accesses device management directly via link</span></li>
|
||||||
|
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_3">Device management as a standalone page feature</span></li>
|
||||||
|
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_4">Need more screen space to display device information</span></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<a href="devices.html" class="demo-btn demo-btn-secondary">
|
||||||
|
<i class="bx bx-arrow-back"></i> <span id="msg_btn_back">Return to Device Management Page</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const SERVER_URL = 'https://local.dqj-macpro.com';
|
||||||
|
|
||||||
|
function getBrowserLanguage() {
|
||||||
|
const lang = window.navigator.language || window.navigator.userLanguage;
|
||||||
|
const supportedLangs = ['en-US', 'zh-CN', 'ja'];
|
||||||
|
if (supportedLangs.includes(lang)) {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
return 'en-US';
|
||||||
|
}
|
||||||
|
|
||||||
|
const CURRENT_LANG = getBrowserLanguage();// 'https://fido2.amipro.me';
|
||||||
|
|
||||||
|
function openStandaloneDefault() {
|
||||||
|
if (!confirm(getI18NText(i18n_messages, 'msg_confirm'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Opening standalone device manager with default style...');
|
||||||
|
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
mode: 'standalone',
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
language: CURRENT_LANG,
|
||||||
|
callbacks: {
|
||||||
|
onInit: function(manager) {
|
||||||
|
console.log('Standalone device manager initialized');
|
||||||
|
},
|
||||||
|
onDeviceAdded: function(device) {
|
||||||
|
console.log('Device added:', device);
|
||||||
|
},
|
||||||
|
onDeviceDeleted: function(deviceId) {
|
||||||
|
console.log('Device deleted:', deviceId);
|
||||||
|
},
|
||||||
|
onDeviceListLoaded: function(devices) {
|
||||||
|
console.log('Device list loaded:', devices.length, 'devices');
|
||||||
|
},
|
||||||
|
onError: function(error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openStandaloneCustom() {
|
||||||
|
if (!confirm(getI18NText(i18n_messages, 'msg_confirm'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Opening standalone device manager with custom style...');
|
||||||
|
|
||||||
|
Fido2UIManager.renderDeviceManager({
|
||||||
|
mode: 'standalone',
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
language: CURRENT_LANG,
|
||||||
|
theme: {
|
||||||
|
logo: 'files/favicon.ico',
|
||||||
|
primaryColor: '#f5576c',
|
||||||
|
backgroundColor: '#fff5f7',
|
||||||
|
textColor: '#9f1239',
|
||||||
|
borderRadius: '12px'
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
showAddButton: true,
|
||||||
|
showDeleteButton: true,
|
||||||
|
showUserInfo: true,
|
||||||
|
showSessionStatus: true
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
onInit: function(manager) {
|
||||||
|
console.log('Custom standalone device manager initialized');
|
||||||
|
},
|
||||||
|
onDeviceAdded: function(device) {
|
||||||
|
console.log('Device added:', device);
|
||||||
|
},
|
||||||
|
onDeviceDeleted: function(deviceId) {
|
||||||
|
console.log('Device deleted:', deviceId);
|
||||||
|
},
|
||||||
|
onDeviceListLoaded: function(devices) {
|
||||||
|
console.log('Device list loaded:', devices.length, 'devices');
|
||||||
|
},
|
||||||
|
onError: function(error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user