mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-06 07:57:04 +08:00
feat: add custom address (#13)
* feat: add custom address * feat: update readme
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
- [x] 使用 Cloudflare Workers 部署后端
|
- [x] 使用 Cloudflare Workers 部署后端
|
||||||
- [x] email 转发使用 Cloudflare Email Routing
|
- [x] email 转发使用 Cloudflare Email Routing
|
||||||
- [x] 使用 password 重新登录之前的邮箱
|
- [x] 使用 password 重新登录之前的邮箱
|
||||||
|
- [x] 获取自定义名字的邮箱
|
||||||
- [ ] 免费版附件过大会造成 Exceeded CPU Limit 错误
|
- [ ] 免费版附件过大会造成 Exceeded CPU Limit 错误
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ This is a temporary email service that uses Cloudflare Workers to create a tempo
|
|||||||
- [x] Deploy the backend with Cloudflare Workers
|
- [x] Deploy the backend with Cloudflare Workers
|
||||||
- [x] Email forwarding using Cloudflare Email Routing
|
- [x] Email forwarding using Cloudflare Email Routing
|
||||||
- [x] Use password to login to the previous mailbox again.
|
- [x] Use password to login to the previous mailbox again.
|
||||||
|
- [x] Get Custom Name Email
|
||||||
- [ ] Exceeded CPU Limit error caused by the free version of the attachment
|
- [ ] Exceeded CPU Limit error caused by the free version of the attachment
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
+1
-1
@@ -27,4 +27,4 @@ coverage
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.env.local
|
.env.*
|
||||||
|
|||||||
+16
-2
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NMessageProvider, NGrid, NBackTop, NLayoutHeader, NInput } from 'naive-ui'
|
import { NMessageProvider, NGrid, NBackTop, NLayoutHeader, NInput } from 'naive-ui'
|
||||||
import { NGi, NSpace, NButton, NConfigProvider, NSelect, NModal } from 'naive-ui'
|
import { NGi, NSpace, NButton, NConfigProvider, NSelect, NModal } from 'naive-ui'
|
||||||
import { darkTheme, NSwitch, NGlobalStyle } from 'naive-ui'
|
import { darkTheme, NSwitch, NGlobalStyle, NPopconfirm } from 'naive-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
@@ -17,6 +17,10 @@ const login = () => {
|
|||||||
jwt.value = password.value;
|
jwt.value = password.value;
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
|
const logout = () => {
|
||||||
|
jwt.value = '';
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -41,7 +45,17 @@ const login = () => {
|
|||||||
Light
|
Light
|
||||||
</template>
|
</template>
|
||||||
</n-switch>
|
</n-switch>
|
||||||
<n-button tertiary @click="showLogin = true" round type="primary">
|
<n-popconfirm v-if="jwt" @positive-click="logout">
|
||||||
|
<template #trigger>
|
||||||
|
<n-button tertiary round type="primary">
|
||||||
|
Logout
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<span>Are you sure to logout?</span>
|
||||||
|
</template>
|
||||||
|
</n-popconfirm>
|
||||||
|
<n-button v-else tertiary @click="showLogin = true" round type="primary">
|
||||||
Login
|
Login
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button tag="a" target="_blank" tertiary type="primary" round
|
<n-button tag="a" target="_blank" tertiary type="primary" round
|
||||||
|
|||||||
+64
-18
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NSpace, NAlert, NSwitch, NCard } from 'naive-ui'
|
import { NSpace, NAlert, NSwitch, NCard, NInput, NInputGroupLabel } from 'naive-ui'
|
||||||
import { NSpin, NButton, NLayout, NPopconfirm, NModal } from 'naive-ui'
|
import { NSpin, NButton, NLayout, NInputGroup, NModal } from 'naive-ui'
|
||||||
import { NList, NListItem, NThing, NTag, NNumberAnimation } from 'naive-ui'
|
import { NList, NListItem, NThing, NTag, NNumberAnimation } from 'naive-ui'
|
||||||
import { watch, onMounted, ref } from "vue";
|
import { watch, onMounted, ref } from "vue";
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
@@ -18,6 +18,12 @@ const data = ref([])
|
|||||||
const API_BASE = import.meta.env.VITE_API_BASE || "";
|
const API_BASE = import.meta.env.VITE_API_BASE || "";
|
||||||
const timer = ref(null)
|
const timer = ref(null)
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
|
const showNewEmail = ref(false)
|
||||||
|
const emailName = ref("")
|
||||||
|
const openSettings = ref({
|
||||||
|
prefix: 'test',
|
||||||
|
domain: 'test.com'
|
||||||
|
})
|
||||||
|
|
||||||
const setupAutoRefresh = async (autoRefresh) => {
|
const setupAutoRefresh = async (autoRefresh) => {
|
||||||
if (autoRefresh) {
|
if (autoRefresh) {
|
||||||
@@ -74,7 +80,11 @@ const copy = async () => {
|
|||||||
const newEmail = async () => {
|
const newEmail = async () => {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const response = await fetch(`${API_BASE}/api/new_address`, {
|
let url = `${API_BASE}/api/new_address`;
|
||||||
|
if (emailName.value) {
|
||||||
|
url = `${url}?name=${emailName.value}`;
|
||||||
|
}
|
||||||
|
const response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
@@ -82,24 +92,38 @@ const newEmail = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
message.error(
|
|
||||||
`${response.status} ${await response.text()}` || "error",
|
|
||||||
);
|
|
||||||
throw new Error(`${response.status} ${await response.text()}` || "error");
|
throw new Error(`${response.status} ${await response.text()}` || "error");
|
||||||
}
|
}
|
||||||
let res = await response.json();
|
let res = await response.json();
|
||||||
jwt.value = res["jwt"];
|
jwt.value = res["jwt"];
|
||||||
|
await refresh();
|
||||||
|
showNewEmail.value = false;
|
||||||
|
showPassword.value = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
jwt.value = "";
|
|
||||||
message.error(error.message || "error");
|
message.error(error.message || "error");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
await refresh();
|
|
||||||
showPassword.value = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getOpenSettings = async (jwt) => {
|
||||||
|
const response = await fetch(`${API_BASE}/open_api/settings`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
message.error(`${response.status} ${await response.text()}` || "error");
|
||||||
|
console.error(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let res = await response.json();
|
||||||
|
openSettings.value = res;
|
||||||
|
}
|
||||||
|
|
||||||
const getSettings = async (jwt) => {
|
const getSettings = async (jwt) => {
|
||||||
if (typeof jwt != 'string' || jwt.trim() === '' || jwt === 'undefined') {
|
if (typeof jwt != 'string' || jwt.trim() === '' || jwt === 'undefined') {
|
||||||
return;
|
return;
|
||||||
@@ -126,6 +150,7 @@ const getSettings = async (jwt) => {
|
|||||||
watch(jwt, async (jwt, old) => getSettings(jwt))
|
watch(jwt, async (jwt, old) => getSettings(jwt))
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
getOpenSettings()
|
||||||
getSettings(jwt.value)
|
getSettings(jwt.value)
|
||||||
await refresh();
|
await refresh();
|
||||||
const token = import.meta.env.VITE_CF_WEB_ANALY_TOKEN;
|
const token = import.meta.env.VITE_CF_WEB_ANALY_TOKEN;
|
||||||
@@ -156,17 +181,12 @@ onMounted(async () => {
|
|||||||
Please click <b>Get New Email</b> button to get a new email address
|
Please click <b>Get New Email</b> button to get a new email address
|
||||||
</span>
|
</span>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-button class="center" @click="showPassword = true" tertiary round type="primary">
|
<n-button v-if="address" class="center" @click="showPassword = true" tertiary round type="primary">
|
||||||
Show Password
|
Show Password
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-popconfirm @positive-click="newEmail" :show-icon="false">
|
<n-button v-else class="center" @click="showNewEmail = true" tertiary round type="primary">
|
||||||
<template #trigger>
|
Get New Email
|
||||||
<n-button class="center" tertiary round type="primary">
|
</n-button>
|
||||||
Get New Email
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
Get New Email?
|
|
||||||
</n-popconfirm>
|
|
||||||
<n-switch v-model:value="autoRefresh">
|
<n-switch v-model:value="autoRefresh">
|
||||||
<template #checked>
|
<template #checked>
|
||||||
Auto Refresh
|
Auto Refresh
|
||||||
@@ -195,6 +215,32 @@ onMounted(async () => {
|
|||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
|
<n-modal v-model:show="showNewEmail" preset="dialog" title="Dialog">
|
||||||
|
<template #header>
|
||||||
|
<div>Get New Email</div>
|
||||||
|
</template>
|
||||||
|
<span>
|
||||||
|
<p>Please input the email you want to use.</p>
|
||||||
|
<p>Levaing it blank will generate a random email address.</p>
|
||||||
|
</span>
|
||||||
|
<n-input-group>
|
||||||
|
<n-input-group-label v-if="openSettings.prefix">
|
||||||
|
{{ openSettings.prefix }}
|
||||||
|
</n-input-group-label>
|
||||||
|
<n-input v-model:value="emailName" />
|
||||||
|
<n-input-group-label>
|
||||||
|
@{{ openSettings.domain }}
|
||||||
|
</n-input-group-label>
|
||||||
|
</n-input-group>
|
||||||
|
<template #action>
|
||||||
|
<n-button @click="showNewEmail = false">
|
||||||
|
Cancel
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="newEmail" type="primary">
|
||||||
|
OK
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
<n-modal v-model:show="showPassword" preset="dialog" title="Dialog">
|
<n-modal v-model:show="showPassword" preset="dialog" title="Dialog">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div>Password</div>
|
<div>Password</div>
|
||||||
|
|||||||
+23
-6
@@ -18,14 +18,31 @@ api.get('/api/settings', async (c) => {
|
|||||||
return c.json(c.get("jwtPayload"));
|
return c.json(c.get("jwtPayload"));
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.get('/open_api/settings', async (c) => {
|
||||||
|
return c.json({
|
||||||
|
"prefix": c.env.PREFIX,
|
||||||
|
"domain": c.env.DOMAIN,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
api.get('/api/new_address', async (c) => {
|
api.get('/api/new_address', async (c) => {
|
||||||
// insert new address
|
// insert new address
|
||||||
const name = Math.random().toString(36).substring(2, 15)
|
let { name } = await c.req.query();
|
||||||
const { success } = await c.env.DB.prepare(
|
if (!name) {
|
||||||
`INSERT INTO address (name) VALUES (?)`
|
name = Math.random().toString(36).substring(2, 15)
|
||||||
).bind(name).run();
|
}
|
||||||
if (!success) {
|
try {
|
||||||
return c.json({ "error": "Failed to create address" }, 500)
|
const { success } = await c.env.DB.prepare(
|
||||||
|
`INSERT INTO address (name) VALUES (?)`
|
||||||
|
).bind(name).run();
|
||||||
|
if (!success) {
|
||||||
|
return c.text("Failed to create address", 500)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message && e.message.includes("UNIQUE")) {
|
||||||
|
return c.text("Please retry a new address", 400)
|
||||||
|
}
|
||||||
|
return c.text("Failed to create address", 500)
|
||||||
}
|
}
|
||||||
// create jwt
|
// create jwt
|
||||||
const jwt = await Jwt.sign({
|
const jwt = await Jwt.sign({
|
||||||
|
|||||||
Reference in New Issue
Block a user