mirror of
https://github.com/schroinerxy/cloud-mail.git
synced 2026-06-21 19:35:50 +08:00
新增支持API添加用户,查询邮件
This commit is contained in:
-197
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+197
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
||||
<link rel="icon" href="/assets/favicon-C5dAZutX.svg" type="image/svg+xml">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
<script type="module" crossorigin src="/assets/index-DQO7jFFS.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BP2DuLPL.css">
|
||||
<script type="module" crossorigin src="/assets/index-ONNky_gH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-NqVTnf-N.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading-first">
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import app from '../hono/hono';
|
||||
import result from '../model/result';
|
||||
import publicService from '../service/public-service';
|
||||
|
||||
app.post('/public/genToken', async (c) => {
|
||||
const data = await publicService.genToken(c, await c.req.json());
|
||||
return c.json(result.ok(data));
|
||||
});
|
||||
|
||||
app.post('/public/emailList', async (c) => {
|
||||
const list = await publicService.emailList(c, await c.req.json());
|
||||
return c.json(result.ok(list));
|
||||
});
|
||||
|
||||
app.post('/public/addUser', async (c) => {
|
||||
await publicService.addUser(c, await c.req.json());
|
||||
return c.json(result.ok());
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
const KvConst = {
|
||||
AUTH_INFO: 'auth-uid:',
|
||||
SETTING: 'setting:',
|
||||
SEND_DAY_COUNT: 'send_day_count:'
|
||||
SEND_DAY_COUNT: 'send_day_count:',
|
||||
PUBLIC_KEY: "public_key:"
|
||||
}
|
||||
|
||||
export default KvConst;
|
||||
|
||||
@@ -33,6 +33,7 @@ export const setting = sqliteTable('setting', {
|
||||
noticeOffset: integer('notice_offset').default(0).notNull(),
|
||||
noticeWidth: integer('notice_width').default(400).notNull(),
|
||||
notice: integer('notice').default(0).notNull(),
|
||||
noRecipient: integer('no_recipient').default(1).notNull()
|
||||
noRecipient: integer('no_recipient').default(1).notNull(),
|
||||
loginDomain: integer('login_domain').default(0).notNull()
|
||||
});
|
||||
export default setting
|
||||
|
||||
@@ -17,4 +17,5 @@ import '../api/all-email-api'
|
||||
import '../api/init-api'
|
||||
import '../api/analysis-api'
|
||||
import '../api/reg-key-api'
|
||||
import '../api/public-api'
|
||||
export default app;
|
||||
|
||||
@@ -59,6 +59,9 @@ const en = {
|
||||
noDomainPermRegKey: "Registration code not valid for this domain",
|
||||
noDomainPermSend: "No permission to send from this domain email",
|
||||
JWTMismatch: 'JWT secret mismatch',
|
||||
publicTokenFail: 'Token validation failed',
|
||||
notAdmin: 'The entered email is not an administrator email',
|
||||
emailExistDatabase: 'Email already exists in the database',
|
||||
perms: {
|
||||
"邮件": "Email",
|
||||
"邮件发送": "Send Email",
|
||||
|
||||
@@ -21,7 +21,7 @@ const resources = {
|
||||
};
|
||||
|
||||
i18next.init({
|
||||
fallbackLng: 'en',
|
||||
fallbackLng: 'zh',
|
||||
resources,
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const zh = {
|
||||
noRegKeyTotal: '注册码使用次数已耗尽',
|
||||
regKeyExpire: '注册码已过期',
|
||||
emailAndPwdEmpty: '邮箱和密码不能为空',
|
||||
notExistUser: '该邮箱不存在',
|
||||
notExistUser: '输入的邮箱不存在',
|
||||
isDelUser: '该邮箱已被注销',
|
||||
isBanUser: '该邮箱已被禁用',
|
||||
regKeyUseCount: '使用次数不能为空',
|
||||
@@ -59,6 +59,9 @@ const zh = {
|
||||
noDomainPermRegKey: '你的注册码没有权限注册该域名邮箱',
|
||||
noDomainPermSend: '你没有权限使用该域名邮箱发送邮件',
|
||||
JWTMismatch: 'jwt_secret 不匹配',
|
||||
publicTokenFail: 'token验证失败',
|
||||
notAdmin: '输入的邮箱不是管理员邮箱',
|
||||
emailExistDatabase: '有邮箱已存在数据库中',
|
||||
perms: {
|
||||
"邮件": "邮件",
|
||||
"邮件发送": "邮件发送",
|
||||
|
||||
@@ -20,10 +20,15 @@ const init = {
|
||||
await this.v1_4DB(c);
|
||||
await this.v1_5DB(c);
|
||||
await this.v1_6DB(c);
|
||||
await this.v1_7DB(c);
|
||||
await settingService.refresh(c);
|
||||
return c.text(t('initSuccess'));
|
||||
},
|
||||
|
||||
async v1_7DB(c) {
|
||||
c.env.db.prepare(`ALTER TABLE setting ADD COLUMN login_domain INTEGER NOT NULL DEFAULT 0;`).run();
|
||||
},
|
||||
|
||||
async v1_6DB(c) {
|
||||
|
||||
const noticeContent = '<div style="color: teal;margin-bottom: 5px;">欢迎使用 Cloud Mail 🎉 </div >\n' +
|
||||
|
||||
@@ -14,7 +14,8 @@ const exclude = [
|
||||
'/file',
|
||||
'/setting/websiteConfig',
|
||||
'/webhooks',
|
||||
'/init'
|
||||
'/init',
|
||||
'/public/genToken'
|
||||
];
|
||||
|
||||
const requirePerms = [
|
||||
@@ -95,6 +96,16 @@ app.use('*', async (c, next) => {
|
||||
return await next();
|
||||
}
|
||||
|
||||
if (path.startsWith('/public')) {
|
||||
|
||||
const userPublicToken = await c.env.kv.get(KvConst.PUBLIC_KEY);
|
||||
const publicToken = c.req.header(constant.TOKEN_HEADER);
|
||||
if (publicToken !== userPublicToken) {
|
||||
throw new BizError(t('publicTokenFail'), 401);
|
||||
}
|
||||
return await next();
|
||||
}
|
||||
|
||||
|
||||
const jwt = c.req.header(constant.TOKEN_HEADER);
|
||||
|
||||
|
||||
@@ -152,6 +152,10 @@ const accountService = {
|
||||
await orm(c).insert(account).values({ ...params }).returning();
|
||||
},
|
||||
|
||||
async insertList(c, list) {
|
||||
await orm(c).insert(account).values(list).run();
|
||||
},
|
||||
|
||||
async physicsDeleteAll(c) {
|
||||
const accountIdsRow = await orm(c).select({accountId: account.accountId}).from(account).where(eq(account.isDel,isDel.DELETE)).limit(99);
|
||||
if (accountIdsRow.length === 0) {
|
||||
|
||||
@@ -119,10 +119,10 @@ const loginService = {
|
||||
|
||||
const userId = await userService.insert(c, { email, regKeyId,password: hash, salt, type: type || defType });
|
||||
|
||||
await userService.updateUserInfo(c, userId, true);
|
||||
|
||||
await accountService.insert(c, { userId: userId, email, name: emailUtils.getName(email) });
|
||||
|
||||
await userService.updateUserInfo(c, userId, true);
|
||||
|
||||
if (regKey !== settingConst.regKey.CLOSE && type) {
|
||||
await regKeyService.reduceCount(c, code, 1);
|
||||
}
|
||||
@@ -136,6 +136,10 @@ const loginService = {
|
||||
|
||||
},
|
||||
|
||||
async registerVerify() {
|
||||
|
||||
},
|
||||
|
||||
async handleOpenRegKey(c, regKey, code) {
|
||||
|
||||
if (!code) {
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
import BizError from '../error/biz-error';
|
||||
import orm from '../entity/orm';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { and, asc, desc, eq, sql } from 'drizzle-orm';
|
||||
import saltHashUtils from '../utils/crypto-utils';
|
||||
import cryptoUtils from '../utils/crypto-utils';
|
||||
import emailUtils from '../utils/email-utils';
|
||||
import roleService from './role-service';
|
||||
import verifyUtils from '../utils/verify-utils';
|
||||
import { t } from '../i18n/i18n';
|
||||
import reqUtils from '../utils/req-utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { isDel, roleConst } from '../const/entity-const';
|
||||
import email from '../entity/email';
|
||||
import userService from './user-service';
|
||||
import KvConst from '../const/kv-const';
|
||||
|
||||
const publicService = {
|
||||
|
||||
async emailList(c, params) {
|
||||
|
||||
let { toEmail, content, subject, sendName, sendEmail, timeSort, num, size, type , isDel } = params
|
||||
|
||||
const query = orm(c).select({
|
||||
emailId: email.emailId,
|
||||
sendEmail: email.sendEmail,
|
||||
sendName: email.name,
|
||||
subject: email.subject,
|
||||
toEmail: email.toEmail,
|
||||
toName: email.toName,
|
||||
type: email.type,
|
||||
createTime: email.createTime,
|
||||
content: email.content,
|
||||
text: email.text,
|
||||
isDel: email.isDel,
|
||||
}).from(email)
|
||||
|
||||
if (!size) {
|
||||
size = 20
|
||||
}
|
||||
|
||||
if (!num) {
|
||||
num = 1
|
||||
}
|
||||
|
||||
size = Number(size);
|
||||
num = Number(num);
|
||||
|
||||
num = (num - 1) * size;
|
||||
|
||||
let conditions = []
|
||||
|
||||
if (toEmail) {
|
||||
conditions.push(sql`${email.toEmail} COLLATE NOCASE LIKE ${toEmail}`)
|
||||
}
|
||||
|
||||
if (sendEmail) {
|
||||
conditions.push(sql`${email.sendEmail} COLLATE NOCASE LIKE ${sendEmail}`)
|
||||
}
|
||||
|
||||
if (sendName) {
|
||||
conditions.push(sql`${email.name} COLLATE NOCASE LIKE ${sendName}`)
|
||||
}
|
||||
|
||||
if (subject) {
|
||||
conditions.push(sql`${email.subject} COLLATE NOCASE LIKE ${subject}`)
|
||||
}
|
||||
|
||||
if (content) {
|
||||
conditions.push(sql`${email.content} COLLATE NOCASE LIKE ${content}`)
|
||||
}
|
||||
|
||||
if (type || type === 0) {
|
||||
conditions.push(eq(email.type, type))
|
||||
}
|
||||
|
||||
if (isDel || isDel === 0) {
|
||||
conditions.push(eq(email.isDel, isDel))
|
||||
}
|
||||
|
||||
if (conditions.length === 1) {
|
||||
query.where(...conditions)
|
||||
} else if (conditions.length > 1) {
|
||||
query.where(and(...conditions))
|
||||
}
|
||||
|
||||
if (timeSort === 'asc') {
|
||||
query.orderBy(asc(email.emailId));
|
||||
} else {
|
||||
query.orderBy(desc(email.emailId));
|
||||
}
|
||||
|
||||
return query.limit(size).offset(num);
|
||||
|
||||
},
|
||||
|
||||
async addUser(c, params) {
|
||||
const { list } = params;
|
||||
|
||||
if (list.length === 0) return;
|
||||
|
||||
for (const emailRow of list) {
|
||||
if (!verifyUtils.isEmail(emailRow.email)) {
|
||||
throw new BizError(t('notEmail'));
|
||||
}
|
||||
|
||||
if (!c.env.domain.includes(emailUtils.getDomain(emailRow.email))) {
|
||||
throw new BizError(t('notEmailDomain'));
|
||||
}
|
||||
|
||||
const { salt, hash } = await saltHashUtils.hashPassword(
|
||||
emailRow.password || cryptoUtils.genRandomPwd()
|
||||
);
|
||||
|
||||
emailRow.salt = salt;
|
||||
emailRow.hash = hash;
|
||||
}
|
||||
|
||||
|
||||
const activeIp = reqUtils.getIp(c);
|
||||
const { os, browser, device } = reqUtils.getUserAgent(c);
|
||||
const activeTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
const roleList = await roleService.roleSelectUse(c);
|
||||
const defRole = roleList.find(roleRow => roleRow.isDefault === roleConst.isDefault.OPEN);
|
||||
|
||||
const userList = [];
|
||||
|
||||
for (const emailRow of list) {
|
||||
let { email, hash, salt, roleName } = emailRow;
|
||||
let type = defRole.roleId;
|
||||
|
||||
if (roleName) {
|
||||
const roleRow = roleList.find(role => role.name === roleName);
|
||||
type = roleRow ? roleRow.roleId : type;
|
||||
}
|
||||
|
||||
const userSql = `INSERT INTO user (email, password, salt, type, os, browser, active_ip, create_ip, device, active_time, create_time)
|
||||
VALUES ('${email}', '${hash}', '${salt}', '${type}', '${os}', '${browser}', '${activeIp}', '${activeIp}', '${device}', '${activeTime}', '${activeTime}')`
|
||||
|
||||
const accountSql = `INSERT INTO account (email, name, user_id)
|
||||
VALUES ('${email}', '${emailUtils.getName(email)}', 0);`;
|
||||
|
||||
userList.push(c.env.db.prepare(userSql));
|
||||
userList.push(c.env.db.prepare(accountSql));
|
||||
|
||||
}
|
||||
|
||||
userList.push(c.env.db.prepare(`UPDATE account SET user_id = (SELECT user_id FROM user WHERE user.email = account.email) WHERE user_id = 0;`))
|
||||
|
||||
try {
|
||||
await c.env.db.batch(userList);
|
||||
} catch (e) {
|
||||
if(e.message.includes('SQLITE_CONSTRAINT')) {
|
||||
throw new BizError(t('emailExistDatabase'))
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async genToken(c, params) {
|
||||
|
||||
await this.verifyUser(c, params)
|
||||
|
||||
const uuid = uuidv4();
|
||||
|
||||
await c.env.kv.put(KvConst.PUBLIC_KEY, uuid, { expirationTtl: 60 * 60 * 24 * 7 });
|
||||
|
||||
return {token: uuid}
|
||||
},
|
||||
|
||||
async verifyUser(c, params) {
|
||||
|
||||
const { email, password } = params
|
||||
|
||||
const userRow = await userService.selectByEmailIncludeDel(c, email);
|
||||
|
||||
if (email !== c.env.admin) {
|
||||
throw new BizError(t('notAdmin'));
|
||||
}
|
||||
|
||||
if (!userRow || userRow.isDel === isDel.DELETE) {
|
||||
throw new BizError(t('notExistUser'));
|
||||
}
|
||||
|
||||
if (!await cryptoUtils.verifyPassword(password, userRow.salt, userRow.password)) {
|
||||
throw new BizError(t('IncorrectPwd'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default publicService
|
||||
@@ -116,7 +116,7 @@ const roleService = {
|
||||
},
|
||||
|
||||
roleSelectUse(c) {
|
||||
return orm(c).select({ name: role.name, roleId: role.roleId }).from(role).orderBy(asc(role.sort)).all();
|
||||
return orm(c).select({ name: role.name, roleId: role.roleId, isDefault: role.isDefault }).from(role).orderBy(asc(role.sort)).all();
|
||||
},
|
||||
|
||||
async selectDefaultRole(c) {
|
||||
@@ -170,6 +170,10 @@ const roleService = {
|
||||
})
|
||||
|
||||
return availIndex > -1
|
||||
},
|
||||
|
||||
selectByName(c, roleName) {
|
||||
return orm(c).select().from(role).where(eq(role.name, roleName)).get();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ const settingService = {
|
||||
noticeWidth: settingRow.noticeWidth,
|
||||
noticeOffset: settingRow.noticeOffset,
|
||||
notice: settingRow.notice,
|
||||
loginDomain: settingRow.loginDomain
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import kvConst from '../const/kv-const';
|
||||
import KvConst from '../const/kv-const';
|
||||
import cryptoUtils from '../utils/crypto-utils';
|
||||
import emailService from './email-service';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import dayjs from 'dayjs';
|
||||
import permService from './perm-service';
|
||||
import roleService from './role-service';
|
||||
@@ -16,6 +15,7 @@ import emailUtils from '../utils/email-utils';
|
||||
import saltHashUtils from '../utils/crypto-utils';
|
||||
import constant from '../const/constant';
|
||||
import { t } from '../i18n/i18n'
|
||||
import reqUtils from '../utils/req-utils';
|
||||
|
||||
const userService = {
|
||||
|
||||
@@ -216,49 +216,22 @@ const userService = {
|
||||
|
||||
async updateUserInfo(c, userId, recordCreateIp = false) {
|
||||
|
||||
const ua = c.req.header('user-agent') || '';
|
||||
console.log(ua);
|
||||
const parser = new UAParser(ua);
|
||||
const { browser, device, os } = parser.getResult();
|
||||
|
||||
let browserInfo = null;
|
||||
let osInfo = null;
|
||||
|
||||
if (browser.name) {
|
||||
browserInfo = browser.name + ' ' + browser.version;
|
||||
}
|
||||
const activeIp = reqUtils.getIp(c);
|
||||
|
||||
if (os.name) {
|
||||
osInfo = os.name + os.version;
|
||||
}
|
||||
|
||||
let deviceInfo = 'Desktop';
|
||||
|
||||
const hasVendor = !!device?.vendor;
|
||||
const hasModel = !!device?.model;
|
||||
|
||||
if (hasVendor || hasModel) {
|
||||
const vendor = device.vendor || '';
|
||||
const model = device.model || '';
|
||||
const type = device.type || '';
|
||||
|
||||
const namePart = [vendor, model].filter(Boolean).join(' ');
|
||||
const typePart = type ? ` (${type})` : '';
|
||||
deviceInfo = (namePart + typePart).trim();
|
||||
}
|
||||
|
||||
const userIp = c.req.header('cf-connecting-ip') || '';
|
||||
const {os, browser, device} = reqUtils.getUserAgent(c);
|
||||
|
||||
const params = {
|
||||
os: osInfo,
|
||||
browser: browserInfo,
|
||||
device: deviceInfo,
|
||||
activeIp: userIp,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
activeIp,
|
||||
activeTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
};
|
||||
|
||||
if (recordCreateIp) {
|
||||
params.createIp = userIp;
|
||||
params.createIp = activeIp;
|
||||
}
|
||||
|
||||
await orm(c)
|
||||
|
||||
@@ -2,13 +2,13 @@ import orm from '../entity/orm';
|
||||
import verifyRecord from '../entity/verify-record';
|
||||
import { eq, sql, and } from 'drizzle-orm';
|
||||
import dayjs from 'dayjs';
|
||||
import ipUtils from '../utils/ip-utils';
|
||||
import reqUtils from '../utils/req-utils';
|
||||
import { verifyRecordType } from '../const/entity-const';
|
||||
|
||||
const verifyRecordService = {
|
||||
|
||||
async selectListByIP(c) {
|
||||
const ip = ipUtils.getIp(c)
|
||||
const ip = reqUtils.getIp(c)
|
||||
return orm(c).select().from(verifyRecord).where(eq(verifyRecord.ip, ip)).all();
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ const verifyRecordService = {
|
||||
|
||||
async isOpenRegVerify(c, regVerifyCount) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
const ip = reqUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.REG))).get();
|
||||
|
||||
@@ -35,7 +35,7 @@ const verifyRecordService = {
|
||||
|
||||
async isOpenAddVerify(c, addVerifyCount) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
const ip = reqUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.ADD))).get();
|
||||
|
||||
@@ -53,7 +53,7 @@ const verifyRecordService = {
|
||||
|
||||
async increaseRegCount(c) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
const ip = reqUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.REG))).get();
|
||||
const now = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
@@ -70,7 +70,7 @@ const verifyRecordService = {
|
||||
|
||||
async increaseAddCount(c) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
const ip = reqUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.ADD))).get();
|
||||
const now = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
@@ -25,6 +25,15 @@ const saltHashUtils = {
|
||||
async verifyPassword(inputPassword, salt, storedHash) {
|
||||
const hash = await this.genHashPassword(inputPassword, salt);
|
||||
return hash === storedHash;
|
||||
},
|
||||
|
||||
genRandomPwd(length = 8) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const ipUtils = {
|
||||
getIp(c) {
|
||||
return c.req.header('CF-Connecting-IP') ||
|
||||
c.req.header('X-Forwarded-For') ||
|
||||
'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export default ipUtils
|
||||
@@ -0,0 +1,45 @@
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
const reqUtils = {
|
||||
getIp(c) {
|
||||
return c.req.header('CF-Connecting-IP') ||
|
||||
c.req.header('X-Forwarded-For') ||
|
||||
'Unknown';
|
||||
},
|
||||
|
||||
getUserAgent(c) {
|
||||
const ua = c.req.header('user-agent') || '';
|
||||
|
||||
const parser = new UAParser(ua);
|
||||
const { browser, device, os } = parser.getResult();
|
||||
|
||||
let browserInfo = null;
|
||||
let osInfo = null;
|
||||
|
||||
if (browser.name) {
|
||||
browserInfo = browser.name + ' ' + browser.version;
|
||||
}
|
||||
|
||||
if (os.name) {
|
||||
osInfo = os.name + os.version;
|
||||
}
|
||||
|
||||
let deviceInfo = 'Desktop';
|
||||
|
||||
const hasVendor = !!device?.vendor;
|
||||
const hasModel = !!device?.model;
|
||||
|
||||
if (hasVendor || hasModel) {
|
||||
const vendor = device.vendor || '';
|
||||
const model = device.model || '';
|
||||
const type = device.type || '';
|
||||
|
||||
const namePart = [vendor, model].filter(Boolean).join(' ');
|
||||
const typePart = type ? ` (${type})` : '';
|
||||
deviceInfo = (namePart + typePart).trim();
|
||||
}
|
||||
|
||||
return {browser: browserInfo || '', device: deviceInfo || '', os: osInfo || ''}
|
||||
}
|
||||
}
|
||||
|
||||
export default reqUtils
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "cloud-mail"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2025-07-29"
|
||||
compatibility_date = "2025-06-04"
|
||||
keep_vars = true
|
||||
|
||||
[observability]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "cloud-mail-dev"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2025-07-29"
|
||||
compatibility_date = "2025-06-04"
|
||||
keep_vars = true
|
||||
|
||||
[observability]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "cloud-mail-test"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2025-07-29"
|
||||
compatibility_date = "2025-06-04"
|
||||
keep_vars = true
|
||||
|
||||
[observability]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "cloud-mail"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2025-07-29"
|
||||
compatibility_date = "2025-06-04"
|
||||
keep_vars = true
|
||||
|
||||
[observability]
|
||||
|
||||
Reference in New Issue
Block a user