Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
c1311f493f | |||
97cbe6e398 | |||
0bb9c5e1e5 | |||
cf90560243 | |||
8def86494a | |||
db46e01f6e | |||
7baf747972 | |||
4a17a1073e | |||
8997ded81d | |||
f177d8e9ab | |||
808a9cc856 | |||
be1c8d1164 | |||
2ecb2f3aa0 | |||
01dcdebda5 | |||
2adcc249de | |||
543e696bfc | |||
796e0204ca | |||
f5a36ab53a |
@ -5,10 +5,32 @@
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "serve.zone",
|
||||
"gitrepo": "platformservice",
|
||||
"description": "contains the platformservice container with mail, sms, letter, ai services.",
|
||||
"description": "A multifaceted platform service handling mail, SMS, letter delivery, and AI services.",
|
||||
"npmPackagename": "@serve.zone/platformservice",
|
||||
"license": "MIT",
|
||||
"projectDomain": "serve.zone"
|
||||
"projectDomain": "serve.zone",
|
||||
"keywords": [
|
||||
"mail service",
|
||||
"SMS",
|
||||
"letter delivery",
|
||||
"AI services",
|
||||
"SMTP server",
|
||||
"mail parsing",
|
||||
"DKIM",
|
||||
"platform service",
|
||||
"mailgun integration",
|
||||
"letterXpress",
|
||||
"OpenAI",
|
||||
"Anthropic AI",
|
||||
"DKIM signing",
|
||||
"mail forwarding",
|
||||
"SMTP TLS",
|
||||
"domain management",
|
||||
"email templating",
|
||||
"rule management",
|
||||
"SMTP STARTTLS",
|
||||
"DNS management"
|
||||
]
|
||||
}
|
||||
},
|
||||
"npmci": {
|
||||
|
59
package.json
59
package.json
@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "@serve.zone/platformservice",
|
||||
"version": "1.0.2",
|
||||
"description": "contains the platformservice container with mail, sms, letter, ai services.",
|
||||
"private": true,
|
||||
"version": "1.0.11",
|
||||
"description": "A multifaceted platform service handling mail, SMS, letter delivery, and AI services.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
@ -9,24 +10,62 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/)",
|
||||
"start": "(node --max_old_space_size=100 ./cli.js)",
|
||||
"start": "(node --max_old_space_size=250 ./cli.js)",
|
||||
"startTs": "(node cli.ts.js)",
|
||||
"build": "(tsbuild --web --allowimplicitany)"
|
||||
"localPublish": ""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.17",
|
||||
"@git.zone/tsrun": "^1.2.8",
|
||||
"@git.zone/tstest": "^1.0.28",
|
||||
"@git.zone/tstest": "^1.0.88",
|
||||
"@git.zone/tswatch": "^2.0.1",
|
||||
"@push.rocks/tapbundle": "^5.0.3"
|
||||
"@push.rocks/tapbundle": "^5.0.22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "^3.0.4",
|
||||
"@api.global/typedserver": "^3.0.20",
|
||||
"@anthropic-ai/sdk": "^0.18.0",
|
||||
"@api.global/typedrequest": "^3.0.19",
|
||||
"@api.global/typedserver": "^3.0.27",
|
||||
"@api.global/typedsocket": "^3.0.0",
|
||||
"@apiclient.xyz/cloudflare": "^6.0.3",
|
||||
"@apiclient.xyz/letterxpress": "^1.0.17",
|
||||
"@push.rocks/projectinfo": "^5.0.1",
|
||||
"@push.rocks/qenv": "^6.0.5",
|
||||
"@push.rocks/smartdata": "^5.0.7",
|
||||
"@push.rocks/smartfile": "^11.0.4",
|
||||
"@push.rocks/smartlog": "^3.0.3",
|
||||
"@push.rocks/smartmail": "^1.0.24",
|
||||
"@push.rocks/smartpath": "^5.0.5",
|
||||
"@push.rocks/smartstate": "^2.0.0"
|
||||
}
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@push.rocks/smartrequest": "^2.0.21",
|
||||
"@push.rocks/smartrx": "^3.0.7",
|
||||
"@push.rocks/smartstate": "^2.0.0",
|
||||
"@serve.zone/interfaces": "^1.0.47",
|
||||
"@tsclass/tsclass": "^4.0.52",
|
||||
"mailauth": "^4.6.5",
|
||||
"mailparser": "^3.6.9",
|
||||
"openai": "^4.29.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
"mail service",
|
||||
"SMS",
|
||||
"letter delivery",
|
||||
"AI services",
|
||||
"SMTP server",
|
||||
"mail parsing",
|
||||
"DKIM",
|
||||
"platform service",
|
||||
"mailgun integration",
|
||||
"letterXpress",
|
||||
"OpenAI",
|
||||
"Anthropic AI",
|
||||
"DKIM signing",
|
||||
"mail forwarding",
|
||||
"SMTP TLS",
|
||||
"domain management",
|
||||
"email templating",
|
||||
"rule management",
|
||||
"SMTP STARTTLS",
|
||||
"DNS management"
|
||||
]
|
||||
}
|
||||
|
4065
pnpm-lock.yaml
generated
4065
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
0
readme.hints.md
Normal file
0
readme.hints.md
Normal file
141
readme.md
141
readme.md
@ -1,31 +1,126 @@
|
||||
# @serve.zone/platformservice
|
||||
|
||||
contains the platformservice container with mail, sms, letter, ai services.
|
||||
|
||||
## Availabililty and Links
|
||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@serve.zone/platformservice)
|
||||
* [gitlab.com (source)](https://gitlab.com/serve.zone/platformservice)
|
||||
* [github.com (source mirror)](https://github.com/serve.zone/platformservice)
|
||||
* [docs (typedoc)](https://serve.zone.gitlab.io/platformservice/)
|
||||
## Install
|
||||
|
||||
## Status for master
|
||||
To install `@serve.zone/platformservice`, run the following command:
|
||||
|
||||
Status Category | Status Badge
|
||||
-- | --
|
||||
GitLab Pipelines | [](https://lossless.cloud)
|
||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
Snyk | [](https://lossless.cloud)
|
||||
TypeScript Support | [](https://lossless.cloud)
|
||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
Code Style | [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||
```sh
|
||||
npm install @serve.zone/platformservice --save
|
||||
```
|
||||
|
||||
Make sure you have Node.js and npm installed on your system to use this package.
|
||||
|
||||
## Usage
|
||||
Use TypeScript for best in class intellisense.
|
||||
For further information read the linked docs at the top of this readme.
|
||||
|
||||
## Legal
|
||||
> MIT licensed | **©** [Task Venture Capital GmbH](https://task.vc)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||
This document provides extensive usage scenarios for the `@serve.zone/platformservice`, a comprehensive ESM module written in TypeScript offering a wide range of services such as mail, SMS, letter, and artificial intelligence (AI) functionalities. This service is an exemplar of a modular design, allowing users to leverage various communication methods and AI services efficiently. Key features provided by this platform include sending and receiving emails, managing SMS services, letter dispatching, and utilizing AI for diverse purposes.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before diving into the examples, ensure you have the platform service installed and configured correctly. The package leverages environment variables for configuration, so you must set up the necessary variables, including service endpoints, authentication tokens, and database connections.
|
||||
|
||||
### Initialization
|
||||
|
||||
First, initialize the platform service, ensuring all dependencies are correctly loaded and configured:
|
||||
|
||||
```ts
|
||||
import { SzPlatformService } from '@serve.zone/platformservice';
|
||||
|
||||
async function initService() {
|
||||
const platformService = new SzPlatformService();
|
||||
await platformService.start();
|
||||
console.log('Platform service initialized successfully.');
|
||||
}
|
||||
|
||||
initService();
|
||||
```
|
||||
|
||||
### Sending Emails
|
||||
|
||||
One of the primary services offered is email management. Here's how to send an email using the platform service:
|
||||
|
||||
```ts
|
||||
import { EmailService, IEmailOptions } from '@serve.zone/platformservice';
|
||||
|
||||
async function sendEmail() {
|
||||
const emailOptions: IEmailOptions = {
|
||||
from: 'no-reply@example.com',
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
body: '<h1>This is a test email</h1>',
|
||||
};
|
||||
|
||||
const emailService = new EmailService('MAILGUN_API_KEY'); // Replace with your real API key
|
||||
await emailService.sendEmail(emailOptions);
|
||||
|
||||
console.log('Email sent successfully.');
|
||||
}
|
||||
|
||||
sendEmail();
|
||||
```
|
||||
|
||||
### Managing SMS
|
||||
|
||||
Similar to email, the platform also facilitates SMS sending:
|
||||
|
||||
```ts
|
||||
import { SmsService, ISmsConstructorOptions } from '@serve.zone/platformservice';
|
||||
|
||||
async function sendSms() {
|
||||
const smsOptions: ISmsConstructorOptions = {
|
||||
apiGatewayApiToken: 'SMS_API_TOKEN', // Replace with your real token
|
||||
};
|
||||
|
||||
const smsService = new SmsService(smsOptions);
|
||||
await smsService.sendSms(1234567890, 'SENDER_NAME', 'This is a test SMS.');
|
||||
|
||||
console.log('SMS sent successfully.');
|
||||
}
|
||||
|
||||
sendSms();
|
||||
```
|
||||
|
||||
### Dispatching Letters
|
||||
|
||||
For physical mail correspondence, the platform provides a letter service:
|
||||
|
||||
```ts
|
||||
import { LetterService, ILetterConstructorOptions } from '@serve.zone/platformservice';
|
||||
|
||||
async function sendLetter() {
|
||||
const letterOptions: ILetterConstructorOptions = {
|
||||
letterxpressUser: 'USER',
|
||||
letterxpressToken: 'TOKEN',
|
||||
};
|
||||
|
||||
const letterService = new LetterService(letterOptions);
|
||||
await letterService.sendLetter('This is a test letter body.', {address: 'Recipient Address', name: 'Recipient Name'});
|
||||
|
||||
console.log('Letter dispatched successfully.');
|
||||
}
|
||||
|
||||
sendLetter();
|
||||
```
|
||||
|
||||
### Leveraging AI Services
|
||||
|
||||
The platform also integrates AI functionalities, allowing for innovative use cases like generating content, analyzing text, or automating responses:
|
||||
|
||||
```ts
|
||||
import { AiService } from '@serve.zone/platformservice';
|
||||
|
||||
async function useAiService() {
|
||||
const aiService = new AiService('OPENAI_API_KEY'); // Replace with your real API key
|
||||
const response = await aiService.generateText('Prompt for the AI service.');
|
||||
|
||||
console.log(`AI response: ${response}`);
|
||||
}
|
||||
|
||||
useAiService();
|
||||
```
|
||||
|
||||
### Conclusion
|
||||
|
||||
The `@serve.zone/platformservice` offers a robust set of features for modern application requirements, including but not limited to communication and AI services. By following the examples above, developers can integrate these services into their applications, harnessing the power of email, SMS, letters, and artificial intelligence seamlessly.
|
||||
undefined
|
5
test/test.ts
Normal file
5
test/test.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
|
||||
tap.test('should create a platform service', async () => {});
|
||||
|
||||
tap.start();
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/platformservice',
|
||||
version: '1.0.2',
|
||||
description: 'contains the platformservice container with mail, sms, letter, ai services.'
|
||||
version: '1.0.11',
|
||||
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
|
||||
}
|
||||
|
50
ts/aibridge/aibridge.classes.aibridge.ts
Normal file
50
ts/aibridge/aibridge.classes.aibridge.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import * as plugins from './aibridge.plugins.js';
|
||||
import * as paths from './aibridge.paths.js';
|
||||
import { AiBridgeDb } from './aibridge.classes.aibridgedb.js';
|
||||
import { OpenAiBridge } from './aibridge.classes.openaibridge.js';
|
||||
|
||||
export class AiBridge {
|
||||
public projectinfo: plugins.projectinfo.ProjectInfo;
|
||||
public serverInstance: plugins.loleServiceserver.ServiceServer;
|
||||
public serviceQenv = new plugins.qenv.Qenv('./', './.nogit');
|
||||
public aibridgeDb: AiBridgeDb;
|
||||
|
||||
public openAiBridge: OpenAiBridge;
|
||||
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public async start() {
|
||||
this.aibridgeDb = new AiBridgeDb(this);
|
||||
await this.aibridgeDb.start();
|
||||
this.projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
this.openAiBridge = new OpenAiBridge(this);
|
||||
await this.openAiBridge.start();
|
||||
|
||||
// server
|
||||
this.serverInstance = new plugins.loleServiceserver.ServiceServer({
|
||||
serviceDomain: 'aibridge.lossless.one',
|
||||
serviceName: 'aibridge',
|
||||
serviceVersion: this.projectinfo.npm.version,
|
||||
addCustomRoutes: async (serverArg) => {
|
||||
// any custom route configs go here
|
||||
},
|
||||
});
|
||||
|
||||
// lets implemenet the actual typedrequest functions
|
||||
this.typedrouter.addTypedHandler<plugins.lointAiBridge.requests.IReq_Chat>(new plugins.typedrequest.TypedHandler('chat', async reqArg => {
|
||||
const resultChat = await this.openAiBridge.chat(reqArg.chat.systemMessage, reqArg.chat.messages[reqArg.chat.messages.length - 1].content, reqArg.chat.messages);
|
||||
return {
|
||||
chat: reqArg.chat,
|
||||
latestMessage: resultChat.message.content,
|
||||
}
|
||||
}))
|
||||
|
||||
await this.serverInstance.start();
|
||||
this.serverInstance.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.serverInstance.stop();
|
||||
await this.aibridgeDb.stop();
|
||||
}
|
||||
}
|
25
ts/aibridge/aibridge.classes.aibridgedb.ts
Normal file
25
ts/aibridge/aibridge.classes.aibridgedb.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import * as plugins from './aibridge.plugins.js';
|
||||
import { AiBridge } from './aibridge.classes.aibridge.js';
|
||||
|
||||
export class AiBridgeDb {
|
||||
public smartdataDb: plugins.smartdata.SmartdataDb;
|
||||
public aibridgeRef: AiBridge;
|
||||
|
||||
constructor(aibridgeRefArg: AiBridge) {
|
||||
this.aibridgeRef = aibridgeRefArg;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.smartdataDb = new plugins.smartdata.SmartdataDb({
|
||||
mongoDbUser: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_USER'),
|
||||
mongoDbName: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_NAME'),
|
||||
mongoDbPass: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_PASS'),
|
||||
mongoDbUrl: await this.aibridgeRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_URL'),
|
||||
});
|
||||
await this.smartdataDb.init();
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.smartdataDb.close();
|
||||
}
|
||||
}
|
58
ts/aibridge/aibridge.classes.openaibridge.ts
Normal file
58
ts/aibridge/aibridge.classes.openaibridge.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { AiBridge } from './aibridge.classes.aibridge.js';
|
||||
import * as plugins from './aibridge.plugins.js';
|
||||
import * as paths from './aibridge.paths.js';
|
||||
|
||||
export class OpenAiBridge {
|
||||
public aiBridgeRef: AiBridge;
|
||||
public openAiApiClient: plugins.openai.default;
|
||||
constructor(aiBridgeRefArg: AiBridge) {
|
||||
this.aiBridgeRef = aiBridgeRefArg;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const openAiToken = await this.aiBridgeRef.serviceQenv.getEnvVarOnDemand('OPENAI_TOKEN');
|
||||
this.openAiApiClient = new plugins.openai.default({
|
||||
apiKey: openAiToken,
|
||||
dangerouslyAllowBrowser: true,
|
||||
});
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
|
||||
public async chat(
|
||||
systemMessage: string,
|
||||
userMessage: string,
|
||||
messageHistory: {
|
||||
role: 'assistant' | 'user';
|
||||
content: string;
|
||||
}[]
|
||||
) {
|
||||
const result = await this.openAiApiClient.chat.completions.create({
|
||||
model: 'gpt-4-turbo-preview',
|
||||
messages: [
|
||||
{ role: 'system', content: systemMessage },
|
||||
...messageHistory,
|
||||
{ role: 'user', content: userMessage },
|
||||
],
|
||||
});
|
||||
return {
|
||||
message: result.choices[0].message,
|
||||
};
|
||||
}
|
||||
|
||||
public async audio(messageArg: string) {
|
||||
const done = plugins.smartpromise.defer();
|
||||
const result = await this.openAiApiClient.audio.speech.create({
|
||||
model: 'tts-1-hd',
|
||||
input: messageArg,
|
||||
voice: 'nova',
|
||||
response_format: 'mp3',
|
||||
speed: 1,
|
||||
});
|
||||
const stream = result.body.pipe(plugins.smartfile.fsStream.createWriteStream(plugins.path.join(paths.nogitDir, 'output.mp3')));
|
||||
stream.on('finish', () => {
|
||||
done.resolve();
|
||||
});
|
||||
return done.promise;
|
||||
}
|
||||
}
|
16
ts/aibridge/aibridge.paths.ts
Normal file
16
ts/aibridge/aibridge.paths.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import * as plugins from './aibridge.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
||||
|
||||
export const assetsDir = plugins.path.join(
|
||||
packageDir,
|
||||
'./assets/'
|
||||
);
|
||||
|
||||
export const nogitDir = plugins.path.join(
|
||||
packageDir,
|
||||
'./.nogit/'
|
||||
);
|
32
ts/aibridge/aibridge.plugins.ts
Normal file
32
ts/aibridge/aibridge.plugins.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
|
||||
// @losslessone_private scope
|
||||
import * as loleServiceserver from '@losslessone_private/lole-serviceserver';
|
||||
import * as lointAiBridge from '@losslessone_private/loint-aibridge';
|
||||
|
||||
export { loleServiceserver, lointAiBridge };
|
||||
|
||||
// apiglobal scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
|
||||
export {
|
||||
typedrequest,
|
||||
}
|
||||
|
||||
// pushrocks scope
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
|
||||
export { projectinfo, qenv, smartdata, smartfile, smartpath, smartpromise };
|
||||
|
||||
// thirdparty scope
|
||||
import * as antrophic from '@anthropic-ai/sdk';
|
||||
import * as openai from 'openai';
|
||||
export { antrophic as anthropic, openai };
|
17
ts/aibridge/index.ts
Normal file
17
ts/aibridge/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { AiBridge } from './aibridge.classes.aibridge.js';
|
||||
|
||||
export {
|
||||
AiBridge,
|
||||
}
|
||||
|
||||
let aibridgeInstance: AiBridge;
|
||||
export const runCli = async () => {
|
||||
aibridgeInstance = new AiBridge();
|
||||
await aibridgeInstance.start();
|
||||
};
|
||||
|
||||
export const stop = async () => {
|
||||
if (aibridgeInstance) {
|
||||
await aibridgeInstance.stop();
|
||||
}
|
||||
};
|
@ -1,6 +1,10 @@
|
||||
import * as plugins from './platformservice.plugins.js';
|
||||
import * as paths from './platformservice.paths.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { PlatformServiceDb } from './classes.platformservicedb.js'
|
||||
import { EmailService } from './email/email.classes.emailservice.js';
|
||||
import { SmsService } from './sms/smsservice.js';
|
||||
import { LetterService } from './letter/classes.letterservice.js';
|
||||
import { MtaService } from './mta/mta.classes.mta.js';
|
||||
|
||||
export class SzPlatformService {
|
||||
public projectinfo: plugins.projectinfo.ProjectInfo;
|
||||
@ -10,9 +14,28 @@ export class SzPlatformService {
|
||||
public typedserver: plugins.typedserver.TypedServer;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
// SubServices
|
||||
public emailService: EmailService;
|
||||
public letterService: LetterService;
|
||||
public mtaService: MtaService;
|
||||
public smsService: SmsService;
|
||||
|
||||
public async start() {
|
||||
this.platformserviceDb = new PlatformServiceDb(this);
|
||||
this.projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
|
||||
// lets start the sub services
|
||||
this.emailService = new EmailService(this);
|
||||
this.letterService = new LetterService(this, {
|
||||
letterxpressUser: await this.serviceQenv.getEnvVarOnDemand('LETTER_API_USER'),
|
||||
letterxpressToken: await this.serviceQenv.getEnvVarOnDemand('LETTER_API_TOKEN')
|
||||
});
|
||||
this.mtaService = new MtaService(this);
|
||||
this.smsService = new SmsService(this, {
|
||||
apiGatewayApiToken: await this.serviceQenv.getEnvVarOnDemand('SMS_API_TOKEN'),
|
||||
});
|
||||
|
||||
// lets start the server finally
|
||||
this.typedserver = new plugins.typedserver.TypedServer({
|
||||
cors: true,
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as plugins from './platformservice.plugins.js';
|
||||
import * as plugins from './plugins.js';
|
||||
import { SzPlatformService } from './classes.platformservice.js';
|
||||
|
||||
|
||||
|
@ -1,17 +1,16 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { request } from 'http';
|
||||
import { logger } from './email.logging.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
export class ApiManager {
|
||||
public emailRef: Email;
|
||||
public emailRef: EmailService;
|
||||
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(emailRefArg: Email) {
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
this.emailRef.mainTypedRouter.addTypedRouter(this.typedRouter);
|
||||
this.typedRouter.addTypedHandler<plugins.lointEmail.IRequestSendEmail>(
|
||||
this.emailRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.platformservice.mta.IRequest_SendEmail>(
|
||||
new plugins.typedrequest.TypedHandler('sendEmail', async (requestData) => {
|
||||
const mailToSend = new plugins.smartmail.Smartmail({
|
||||
body: requestData.body,
|
||||
@ -22,7 +21,7 @@ export class ApiManager {
|
||||
if (requestData.attachments) {
|
||||
for (const attachment of requestData.attachments) {
|
||||
mailToSend.addAttachment(
|
||||
await plugins.smartfile.Smartfile.fromString(
|
||||
await plugins.smartfile.SmartFile.fromString(
|
||||
attachment.name,
|
||||
attachment.binaryAttachmentString,
|
||||
'binary'
|
||||
|
@ -1,11 +1,11 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
|
||||
export class MailgunConnector {
|
||||
public emailRef: Email;
|
||||
public emailRef: EmailService;
|
||||
public mailgunAccount: plugins.mailgun.MailgunAccount;
|
||||
|
||||
constructor(emailRefArg: Email) {
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
this.mailgunAccount = new plugins.mailgun.MailgunAccount({
|
||||
apiToken: this.emailRef.qenv.getEnvVarOnDemand('MAILGUN_API_TOKEN'),
|
||||
|
@ -1,16 +1,21 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import * as paths from './email.paths.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { MailgunConnector } from './email.classes.connector.mailgun.js';
|
||||
import { RuleManager } from './email.classes.rulemanager.js';
|
||||
import { ApiManager } from './email.classes.apimanager.js';
|
||||
import { logger } from './email.logging.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { SzPlatformService } from '../classes.platformservice.js';
|
||||
|
||||
export class Email {
|
||||
|
||||
export interface IEmailConstructorOptions {
|
||||
mailgunApiKey: string;
|
||||
}
|
||||
|
||||
export class EmailService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
|
||||
// typedrouter
|
||||
public mainTypedRouter = new plugins.typedrequest.TypedRouter();
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
// connectors
|
||||
public mailgunConnector: MailgunConnector;
|
||||
@ -22,11 +27,12 @@ export class Email {
|
||||
|
||||
constructor(platformServiceRefArg: SzPlatformService) {
|
||||
this.platformServiceRef = platformServiceRefArg;
|
||||
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.mailgunConnector = new MailgunConnector(this);
|
||||
this.ruleManager = new RuleManager(this);
|
||||
this.platformServiceRef.typedserver.server.addRoute(
|
||||
'/mailgun-notify',
|
||||
new plugins.loleServiceserver.Handler('POST', async (req, res) => {
|
||||
new plugins.typedserver.servertools.Handler('POST', async (req, res) => {
|
||||
console.log('Got a mailgun email notification');
|
||||
res.status(200);
|
||||
res.end();
|
@ -1,14 +1,14 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
import { logger } from './email.logging.js';
|
||||
|
||||
export class RuleManager {
|
||||
public emailRef: Email;
|
||||
public emailRef: EmailService;
|
||||
public smartruleInstance = new plugins.smartrule.SmartRule<
|
||||
plugins.smartmail.Smartmail<plugins.mailgun.IMailgunMessage>
|
||||
>();
|
||||
|
||||
constructor(emailRefArg: Email) {
|
||||
constructor(emailRefArg: EmailService) {
|
||||
this.emailRef = emailRefArg;
|
||||
}
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
import * as paths from './email.paths.js';
|
||||
|
||||
const projectInfoNpm = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
|
||||
|
||||
export const logger = plugins.loleLog.createLoleLogger({
|
||||
companyUnit: 'lossless.cloud',
|
||||
containerName: 'email',
|
||||
containerVersion: projectInfoNpm.version,
|
||||
sentryAppName: 'email',
|
||||
sentryDsn: 'https://7037e86f36134ced85ae56a57daa1e5e@o169278.ingest.sentry.io/5280282',
|
||||
zone: 'servezone',
|
||||
});
|
@ -1,6 +0,0 @@
|
||||
import * as plugins from './email.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
@ -1,43 +0,0 @@
|
||||
// native scope
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
|
||||
// @losslessone_private scope
|
||||
import * as loleLog from '@losslessone_private/lole-log';
|
||||
import * as loleServiceserver from '@losslessone_private/lole-serviceserver';
|
||||
|
||||
import * as lointEmail from '@losslessone_private/loint-email';
|
||||
|
||||
export { loleLog, loleServiceserver, lointEmail };
|
||||
|
||||
// @apiglobal scope
|
||||
import * as typedrequest from '@apiglobal/typedrequest';
|
||||
|
||||
export { typedrequest };
|
||||
|
||||
// @mojoio scope
|
||||
import * as mailgun from '@mojoio/mailgun';
|
||||
|
||||
export { mailgun };
|
||||
|
||||
// @pushrocks scope
|
||||
import * as projectinfo from '@pushrocks/projectinfo';
|
||||
import * as qenv from '@pushrocks/qenv';
|
||||
import * as smartfile from '@pushrocks/smartfile';
|
||||
import * as smartmail from '@pushrocks/smartmail';
|
||||
import * as smartpath from '@pushrocks/smartpath';
|
||||
import * as smartrequest from '@pushrocks/smartrequest';
|
||||
import * as smartrule from '@pushrocks/smartrule';
|
||||
import * as smartvalidator from '@pushrocks/smartvalidator';
|
||||
|
||||
export {
|
||||
projectinfo,
|
||||
qenv,
|
||||
smartfile,
|
||||
smartmail,
|
||||
smartpath,
|
||||
smartrequest,
|
||||
smartrule,
|
||||
smartvalidator,
|
||||
};
|
@ -1,3 +1,3 @@
|
||||
import { Email } from './email.classes.email.js';
|
||||
import { EmailService } from './email.classes.emailservice.js';
|
||||
|
||||
export { Email };
|
||||
export { EmailService as Email };
|
@ -1,4 +1,4 @@
|
||||
export * from './00_commitinfo_data.js';
|
||||
import { SzPlatformService } from './classes.platformservice.js'
|
||||
import { SzPlatformService } from './classes.platformservice.js';
|
||||
|
||||
export const runCli = async () => {}
|
41
ts/letter/classes.letterservice.ts
Normal file
41
ts/letter/classes.letterservice.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { SzPlatformService } from '../classes.platformservice.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export interface ILetterConstructorOptions {
|
||||
letterxpressUser: string;
|
||||
letterxpressToken: string;
|
||||
}
|
||||
|
||||
export class LetterService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
public options: ILetterConstructorOptions;
|
||||
public letterxpressAccount: plugins.letterxpress.LetterXpressAccount;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(platformServiceRefArg: SzPlatformService, optionsArg: ILetterConstructorOptions) {
|
||||
this.platformServiceRef = platformServiceRefArg;
|
||||
this.options = optionsArg;
|
||||
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler<
|
||||
plugins.servezoneInterfaces.platformservice.letter.IRequest_SendLetter
|
||||
>(new plugins.typedrequest.TypedHandler('sendLetter', async dataArg => {
|
||||
if(dataArg.needsCover) {
|
||||
|
||||
}
|
||||
return {
|
||||
processId: '',
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.letterxpressAccount = new plugins.letterxpress.LetterXpressAccount({
|
||||
username: this.options.letterxpressUser,
|
||||
apiKey: this.options.letterxpressToken,
|
||||
});
|
||||
await this.letterxpressAccount.start();
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
}
|
0
ts/letter/index.ts
Normal file
0
ts/letter/index.ts
Normal file
9
ts/logger.ts
Normal file
9
ts/logger.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
environment: 'production',
|
||||
runtime: 'node',
|
||||
zone: 'serve.zone',
|
||||
}
|
||||
});
|
8
ts/mta/index.ts
Normal file
8
ts/mta/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './mta.classes.dkimcreator.js';
|
||||
export * from './mta.classes.emailsignjob.js';
|
||||
export * from './mta.classes.dkimverifier.js';
|
||||
export * from './mta.classes.mta.js';
|
||||
export * from './mta.classes.smtpserver.js';
|
||||
export * from './mta.classes.emailsendjob.js';
|
||||
export * from './mta.classes.mta.js';
|
||||
export * from './mta.classes.email.js';
|
7
ts/mta/mta.classes.apimanager.ts
Normal file
7
ts/mta/mta.classes.apimanager.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class ApiManager {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
|
||||
}
|
119
ts/mta/mta.classes.dkimcreator.ts
Normal file
119
ts/mta/mta.classes.dkimcreator.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import type { MtaService } from './mta.classes.mta.js';
|
||||
|
||||
const readFile = plugins.util.promisify(plugins.fs.readFile);
|
||||
const writeFile = plugins.util.promisify(plugins.fs.writeFile);
|
||||
const generateKeyPair = plugins.util.promisify(plugins.crypto.generateKeyPair);
|
||||
|
||||
export interface IKeyPaths {
|
||||
privateKeyPath: string;
|
||||
publicKeyPath: string;
|
||||
}
|
||||
|
||||
export class DKIMCreator {
|
||||
private keysDir: string;
|
||||
|
||||
constructor(metaRef: MtaService, keysDir = paths.keysDir) {
|
||||
this.keysDir = keysDir;
|
||||
}
|
||||
|
||||
public async getKeyPathsForDomain(domainArg: string): Promise<IKeyPaths> {
|
||||
return {
|
||||
privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`),
|
||||
publicKeyPath: plugins.path.join(this.keysDir, `${domainArg}-public.pem`),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if a DKIM key is present and creates one and stores it to disk otherwise
|
||||
public async handleDKIMKeysForDomain(domainArg: string): Promise<void> {
|
||||
try {
|
||||
await this.readDKIMKeys(domainArg);
|
||||
} catch (error) {
|
||||
console.log(`No DKIM keys found for ${domainArg}. Generating...`);
|
||||
await this.createAndStoreDKIMKeys(domainArg);
|
||||
const dnsValue = await this.getDNSRecordForDomain(domainArg);
|
||||
plugins.smartfile.fs.ensureDirSync(paths.dnsRecordsDir);
|
||||
plugins.smartfile.memory.toFsSync(JSON.stringify(dnsValue, null, 2), plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`));
|
||||
}
|
||||
}
|
||||
|
||||
public async handleDKIMKeysForEmail(email: Email): Promise<void> {
|
||||
const domain = email.from.split('@')[1];
|
||||
await this.handleDKIMKeysForDomain(domain);
|
||||
}
|
||||
|
||||
// Read DKIM keys from disk
|
||||
public async readDKIMKeys(domainArg: string): Promise<{ privateKey: string; publicKey: string }> {
|
||||
const keyPaths = await this.getKeyPathsForDomain(domainArg);
|
||||
const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
|
||||
readFile(keyPaths.privateKeyPath),
|
||||
readFile(keyPaths.publicKeyPath),
|
||||
]);
|
||||
|
||||
// Convert the buffers to strings
|
||||
const privateKey = privateKeyBuffer.toString();
|
||||
const publicKey = publicKeyBuffer.toString();
|
||||
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
// Create a DKIM key pair
|
||||
private async createDKIMKeys(): Promise<{ privateKey: string; publicKey: string }> {
|
||||
const { privateKey, publicKey } = await generateKeyPair('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
||||
privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
|
||||
});
|
||||
|
||||
return { privateKey, publicKey };
|
||||
}
|
||||
|
||||
// Store a DKIM key pair to disk
|
||||
private async storeDKIMKeys(
|
||||
privateKey: string,
|
||||
publicKey: string,
|
||||
privateKeyPath: string,
|
||||
publicKeyPath: string
|
||||
): Promise<void> {
|
||||
await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
|
||||
}
|
||||
|
||||
// Create a DKIM key pair and store it to disk
|
||||
private async createAndStoreDKIMKeys(domain: string): Promise<void> {
|
||||
const { privateKey, publicKey } = await this.createDKIMKeys();
|
||||
const keyPaths = await this.getKeyPathsForDomain(domain);
|
||||
await this.storeDKIMKeys(
|
||||
privateKey,
|
||||
publicKey,
|
||||
keyPaths.privateKeyPath,
|
||||
keyPaths.publicKeyPath
|
||||
);
|
||||
console.log(`DKIM keys for ${domain} created and stored.`);
|
||||
}
|
||||
|
||||
private async getDNSRecordForDomain(domainArg: string): Promise<plugins.tsclass.network.IDnsRecord> {
|
||||
await this.handleDKIMKeysForDomain(domainArg);
|
||||
const keys = await this.readDKIMKeys(domainArg);
|
||||
|
||||
// Remove the PEM header and footer and newlines
|
||||
const pemHeader = '-----BEGIN PUBLIC KEY-----';
|
||||
const pemFooter = '-----END PUBLIC KEY-----';
|
||||
const keyContents = keys.publicKey
|
||||
.replace(pemHeader, '')
|
||||
.replace(pemFooter, '')
|
||||
.replace(/\n/g, '');
|
||||
|
||||
// Now generate the DKIM DNS TXT record
|
||||
const dnsRecordValue = `v=DKIM1; h=sha256; k=rsa; p=${keyContents}`;
|
||||
|
||||
return {
|
||||
name: `mta._domainkey.${domainArg}`,
|
||||
type: 'TXT',
|
||||
dnsSecEnabled: null,
|
||||
value: dnsRecordValue,
|
||||
};
|
||||
}
|
||||
}
|
35
ts/mta/mta.classes.dkimverifier.ts
Normal file
35
ts/mta/mta.classes.dkimverifier.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { MtaService } from './mta.classes.mta.js';
|
||||
|
||||
class DKIMVerifier {
|
||||
public mtaRef: MtaService;
|
||||
|
||||
constructor(mtaRefArg: MtaService) {
|
||||
this.mtaRef = mtaRefArg;
|
||||
}
|
||||
|
||||
async verify(email: string): Promise<boolean> {
|
||||
console.log('Trying to verify DKIM now...');
|
||||
|
||||
try {
|
||||
const verification = await plugins.mailauth.authenticate(email, {
|
||||
/* resolver: (...args) => {
|
||||
console.log(args);
|
||||
} */
|
||||
});
|
||||
console.log(verification);
|
||||
if (verification && verification.dkim.results[0].status.result === 'pass') {
|
||||
console.log('DKIM Verification result: pass');
|
||||
return true;
|
||||
} else {
|
||||
console.error('DKIM Verification failed:', verification?.error || 'Unknown error');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('DKIM Verification failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { DKIMVerifier };
|
10
ts/mta/mta.classes.dnsmanager.ts
Normal file
10
ts/mta/mta.classes.dnsmanager.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { MtaService } from './mta.classes.mta.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class DNSManager {
|
||||
public mtaRef: MtaService;
|
||||
|
||||
constructor(mtaRefArg: MtaService) {
|
||||
this.mtaRef = mtaRefArg;
|
||||
}
|
||||
}
|
36
ts/mta/mta.classes.email.ts
Normal file
36
ts/mta/mta.classes.email.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export interface IAttachment {
|
||||
filename: string;
|
||||
content: Buffer;
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
export interface IEmailOptions {
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
attachments: IAttachment[];
|
||||
mightBeSpam?: boolean;
|
||||
}
|
||||
|
||||
export class Email {
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
attachments: IAttachment[];
|
||||
mightBeSpam: boolean;
|
||||
|
||||
constructor(options: IEmailOptions) {
|
||||
this.from = options.from;
|
||||
this.to = options.to;
|
||||
this.subject = options.subject;
|
||||
this.text = options.text;
|
||||
this.attachments = options.attachments;
|
||||
this.mightBeSpam = options.mightBeSpam || false;
|
||||
}
|
||||
|
||||
public getFromDomain() {
|
||||
return this.from.split('@')[1]
|
||||
}
|
||||
}
|
173
ts/mta/mta.classes.emailsendjob.ts
Normal file
173
ts/mta/mta.classes.emailsendjob.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import { EmailSignJob } from './mta.classes.emailsignjob.js';
|
||||
import type { MtaService } from './mta.classes.mta.js';
|
||||
|
||||
export class EmailSendJob {
|
||||
mtaRef: MtaService;
|
||||
private email: Email;
|
||||
private socket: plugins.net.Socket | plugins.tls.TLSSocket = null;
|
||||
private mxRecord: string = null;
|
||||
|
||||
constructor(mtaRef: MtaService, emailArg: Email) {
|
||||
this.email = emailArg;
|
||||
this.mtaRef = mtaRef;
|
||||
}
|
||||
|
||||
async send(): Promise<void> {
|
||||
const domain = this.email.to.split('@')[1];
|
||||
const addresses = await this.resolveMx(domain);
|
||||
addresses.sort((a, b) => a.priority - b.priority);
|
||||
this.mxRecord = addresses[0].exchange;
|
||||
|
||||
console.log(`Using ${this.mxRecord} as mail server for domain ${domain}`);
|
||||
|
||||
this.socket = plugins.net.connect(25, this.mxRecord);
|
||||
await this.processInitialResponse();
|
||||
await this.sendCommand(`EHLO ${this.email.from.split('@')[1]}\r\n`, '250');
|
||||
try {
|
||||
await this.sendCommand('STARTTLS\r\n', '220');
|
||||
this.socket = plugins.tls.connect({ socket: this.socket, rejectUnauthorized: false });
|
||||
await this.processTLSUpgrade(this.email.from.split('@')[1]);
|
||||
} catch (error) {
|
||||
console.log('Error sending STARTTLS command:', error);
|
||||
console.log('Continuing with unencrypted connection...');
|
||||
}
|
||||
|
||||
await this.sendMessage();
|
||||
}
|
||||
|
||||
private resolveMx(domain: string): Promise<plugins.dns.MxRecord[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
plugins.dns.resolveMx(domain, (err, addresses) => {
|
||||
if (err) {
|
||||
console.error('Error resolving MX:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(addresses);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private processInitialResponse(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.once('data', (data) => {
|
||||
const response = data.toString();
|
||||
if (!response.startsWith('220')) {
|
||||
console.error('Unexpected initial server response:', response);
|
||||
reject(new Error(`Unexpected initial server response: ${response}`));
|
||||
} else {
|
||||
console.log('Received initial server response:', response);
|
||||
console.log('Connected to server, sending EHLO...');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private processTLSUpgrade(domain: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.once('secureConnect', async () => {
|
||||
console.log('TLS started successfully');
|
||||
try {
|
||||
await this.sendCommand(`EHLO ${domain}\r\n`, '250');
|
||||
resolve();
|
||||
} catch (err) {
|
||||
console.error('Error sending EHLO after TLS upgrade:', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private sendCommand(command: string, expectedResponseCode?: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.write(command, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!expectedResponseCode) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket.once('data', (data) => {
|
||||
const response = data.toString();
|
||||
if (response.startsWith('221')) {
|
||||
this.socket.destroy();
|
||||
resolve();
|
||||
}
|
||||
if (!response.startsWith(expectedResponseCode)) {
|
||||
reject(new Error(`Unexpected server response: ${response}`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async sendMessage(): Promise<void> {
|
||||
console.log('Preparing email message...');
|
||||
const messageId = `<${plugins.uuid.v4()}@${this.email.from.split('@')[1]}>`;
|
||||
|
||||
// Create a boundary for the email parts
|
||||
const boundary = '----=_NextPart_' + plugins.uuid.v4();
|
||||
|
||||
const headers = {
|
||||
From: this.email.from,
|
||||
To: this.email.to,
|
||||
Subject: this.email.subject,
|
||||
'Content-Type': `multipart/mixed; boundary="${boundary}"`,
|
||||
};
|
||||
|
||||
// Construct the body of the message
|
||||
let body = `--${boundary}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n${this.email.text}\r\n`;
|
||||
|
||||
// Then, the attachments
|
||||
for (let attachment of this.email.attachments) {
|
||||
body += `--${boundary}\r\nContent-Type: ${attachment.contentType}; name="${attachment.filename}"\r\n`;
|
||||
body += 'Content-Transfer-Encoding: base64\r\n';
|
||||
body += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n\r\n`;
|
||||
body += attachment.content.toString('base64') + '\r\n';
|
||||
}
|
||||
|
||||
// End of email
|
||||
body += `--${boundary}--\r\n`;
|
||||
|
||||
// Create an instance of DKIMSigner
|
||||
const dkimSigner = new EmailSignJob(this.mtaRef, {
|
||||
domain: this.email.getFromDomain(), // Replace with your domain
|
||||
selector: `mta`, // Replace with your DKIM selector
|
||||
headers: headers,
|
||||
body: body,
|
||||
});
|
||||
|
||||
// Construct the message with DKIM-Signature header
|
||||
let message = `Message-ID: ${messageId}\r\nFrom: ${this.email.from}\r\nTo: ${this.email.to}\r\nSubject: ${this.email.subject}\r\nContent-Type: multipart/mixed; boundary="${boundary}"\r\n\r\n`;
|
||||
message += body;
|
||||
|
||||
let signatureHeader = await dkimSigner.getSignatureHeader(message);
|
||||
message = `${signatureHeader}${message}`;
|
||||
|
||||
plugins.smartfile.fs.ensureDirSync(paths.sentEmailsDir);
|
||||
plugins.smartfile.memory.toFsSync(message, plugins.path.join(paths.sentEmailsDir, `${Date.now()}.eml`));
|
||||
|
||||
|
||||
// Adding necessary commands before sending the actual email message
|
||||
await this.sendCommand(`MAIL FROM:<${this.email.from}>\r\n`, '250');
|
||||
await this.sendCommand(`RCPT TO:<${this.email.to}>\r\n`, '250');
|
||||
await this.sendCommand(`DATA\r\n`, '354');
|
||||
|
||||
// Now send the message content
|
||||
await this.sendCommand(message);
|
||||
await this.sendCommand('\r\n.\r\n', '250');
|
||||
|
||||
await this.sendCommand('QUIT\r\n', '221');
|
||||
console.log('Email message sent successfully!');
|
||||
}
|
||||
}
|
69
ts/mta/mta.classes.emailsignjob.ts
Normal file
69
ts/mta/mta.classes.emailsignjob.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { MtaService } from './mta.classes.mta.js';
|
||||
|
||||
interface Headers {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface IEmailSignJobOptions {
|
||||
domain: string;
|
||||
selector: string;
|
||||
headers: Headers;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export class EmailSignJob {
|
||||
mtaRef: MtaService;
|
||||
jobOptions: IEmailSignJobOptions;
|
||||
|
||||
constructor(mtaRefArg: MtaService, options: IEmailSignJobOptions) {
|
||||
this.mtaRef = mtaRefArg;
|
||||
this.jobOptions = options;
|
||||
}
|
||||
|
||||
async loadPrivateKey(): Promise<string> {
|
||||
return plugins.fs.promises.readFile(
|
||||
(await this.mtaRef.dkimCreator.getKeyPathsForDomain(this.jobOptions.domain)).privateKeyPath,
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
public async getSignatureHeader(emailMessage: string): Promise<string> {
|
||||
const signResult = await plugins.dkimSign(emailMessage, {
|
||||
// Optional, default canonicalization, default is "relaxed/relaxed"
|
||||
canonicalization: 'relaxed/relaxed', // c=
|
||||
|
||||
// Optional, default signing and hashing algorithm
|
||||
// Mostly useful when you want to use rsa-sha1, otherwise no need to set
|
||||
algorithm: 'rsa-sha256',
|
||||
|
||||
// Optional, default is current time
|
||||
signTime: new Date(), // t=
|
||||
|
||||
// Keys for one or more signatures
|
||||
// Different signatures can use different algorithms (mostly useful when
|
||||
// you want to sign a message both with RSA and Ed25519)
|
||||
signatureData: [
|
||||
{
|
||||
signingDomain: this.jobOptions.domain, // d=
|
||||
selector: this.jobOptions.selector, // s=
|
||||
// supported key types: RSA, Ed25519
|
||||
privateKey: await this.loadPrivateKey(), // k=
|
||||
|
||||
// Optional algorithm, default is derived from the key.
|
||||
// Overrides whatever was set in parent object
|
||||
algorithm: 'rsa-sha256',
|
||||
|
||||
// Optional signature specifc canonicalization, overrides whatever was set in parent object
|
||||
canonicalization: 'relaxed/relaxed', // c=
|
||||
|
||||
// Maximum number of canonicalized body bytes to sign (eg. the "l=" tag).
|
||||
// Do not use though. This is available only for compatibility testing.
|
||||
// maxBodyLength: 12345
|
||||
},
|
||||
],
|
||||
});
|
||||
const signature = signResult.signatures;
|
||||
return signature;
|
||||
}
|
||||
}
|
69
ts/mta/mta.classes.mta.ts
Normal file
69
ts/mta/mta.classes.mta.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import { EmailSendJob } from './mta.classes.emailsendjob.js';
|
||||
import { DKIMCreator } from './mta.classes.dkimcreator.js';
|
||||
import { DKIMVerifier } from './mta.classes.dkimverifier.js';
|
||||
import { SMTPServer } from './mta.classes.smtpserver.js';
|
||||
import { DNSManager } from './mta.classes.dnsmanager.js';
|
||||
import type { SzPlatformService } from '../classes.platformservice.js';
|
||||
|
||||
export class MtaService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
public server: SMTPServer;
|
||||
public dkimCreator: DKIMCreator;
|
||||
public dkimVerifier: DKIMVerifier;
|
||||
public dnsManager: DNSManager;
|
||||
|
||||
constructor(platformServiceRefArg: SzPlatformService) {
|
||||
this.platformServiceRef = platformServiceRefArg;
|
||||
this.dkimCreator = new DKIMCreator(this);
|
||||
this.dkimVerifier = new DKIMVerifier(this);
|
||||
this.dnsManager = new DNSManager(this);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets get the certificate
|
||||
/**
|
||||
* gets a certificate for a domain used by a service
|
||||
* @param serviceNameArg
|
||||
* @param domainNameArg
|
||||
*/
|
||||
const typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
const typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
|
||||
typedrouter,
|
||||
'https://cloudly.lossless.one:443'
|
||||
);
|
||||
const getCertificateForDomainOverHttps = async (domainNameArg: string) => {
|
||||
const typedCertificateRequest =
|
||||
typedsocketClient.createTypedRequest<any>('getSslCertificate');
|
||||
const typedResponse = await typedCertificateRequest.fire({
|
||||
authToken: '', // do proper auth here
|
||||
requiredCertName: domainNameArg,
|
||||
});
|
||||
return typedResponse.certificate;
|
||||
};
|
||||
const certificate = await getCertificateForDomainOverHttps('mta.lossless.one');
|
||||
await typedsocketClient.stop();
|
||||
this.server = new SMTPServer(this, {
|
||||
port: 25,
|
||||
key: certificate.privateKey,
|
||||
cert: certificate.publicKey,
|
||||
});
|
||||
await this.server.start();
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
if (!this.server) {
|
||||
console.error('Server is not running');
|
||||
return;
|
||||
}
|
||||
await this.server.stop();
|
||||
}
|
||||
|
||||
public async send(email: Email): Promise<void> {
|
||||
await this.dkimCreator.handleDKIMKeysForEmail(email);
|
||||
const sendJob = new EmailSendJob(this, email);
|
||||
await sendJob.send();
|
||||
}
|
||||
}
|
191
ts/mta/mta.classes.smtpserver.ts
Normal file
191
ts/mta/mta.classes.smtpserver.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import type { MtaService } from './mta.classes.mta.js';
|
||||
|
||||
export interface ISmtpServerOptions {
|
||||
port: number;
|
||||
key: string;
|
||||
cert: string;
|
||||
}
|
||||
|
||||
export class SMTPServer {
|
||||
public mtaRef: MtaService;
|
||||
private smtpServerOptions: ISmtpServerOptions;
|
||||
private server: plugins.net.Server;
|
||||
private emailBufferStringMap: Map<plugins.net.Socket, string>;
|
||||
|
||||
constructor(mtaRefArg: MtaService, optionsArg: ISmtpServerOptions) {
|
||||
console.log('SMTPServer instance is being created...');
|
||||
|
||||
this.mtaRef = mtaRefArg;
|
||||
this.smtpServerOptions = optionsArg;
|
||||
this.emailBufferStringMap = new Map();
|
||||
|
||||
this.server = plugins.net.createServer((socket) => {
|
||||
console.log('New connection established...');
|
||||
|
||||
socket.write('220 mta.lossless.one ESMTP Postfix\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
this.processData(socket, data);
|
||||
});
|
||||
|
||||
socket.on('end', () => {
|
||||
console.log('Socket closed. Deleting related emailBuffer...');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
console.error('Socket error occurred. Deleting related emailBuffer...');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log('Connection was closed by the client');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private startTLS(socket: plugins.net.Socket) {
|
||||
const secureContext = plugins.tls.createSecureContext({
|
||||
key: this.smtpServerOptions.key,
|
||||
cert: this.smtpServerOptions.cert,
|
||||
});
|
||||
|
||||
const tlsSocket = new plugins.tls.TLSSocket(socket, {
|
||||
secureContext: secureContext,
|
||||
isServer: true,
|
||||
});
|
||||
|
||||
tlsSocket.on('secure', () => {
|
||||
console.log('Connection secured.');
|
||||
this.emailBufferStringMap.set(tlsSocket, this.emailBufferStringMap.get(socket) || '');
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
// Use the same handler for the 'data' event as for the unsecured socket.
|
||||
tlsSocket.on('data', (data: Buffer) => {
|
||||
this.processData(tlsSocket, Buffer.from(data));
|
||||
});
|
||||
|
||||
tlsSocket.on('end', () => {
|
||||
console.log('TLS socket closed. Deleting related emailBuffer...');
|
||||
this.emailBufferStringMap.delete(tlsSocket);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', (err) => {
|
||||
console.error('TLS socket error occurred. Deleting related emailBuffer...');
|
||||
this.emailBufferStringMap.delete(tlsSocket);
|
||||
});
|
||||
}
|
||||
|
||||
private processData(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: Buffer) {
|
||||
const dataString = data.toString();
|
||||
console.log(`Received data:`);
|
||||
console.log(`${dataString}`)
|
||||
|
||||
if (dataString.startsWith('EHLO')) {
|
||||
socket.write('250-mta.lossless.one Hello\r\n250 STARTTLS\r\n');
|
||||
} else if (dataString.startsWith('MAIL FROM')) {
|
||||
socket.write('250 Ok\r\n');
|
||||
} else if (dataString.startsWith('RCPT TO')) {
|
||||
socket.write('250 Ok\r\n');
|
||||
} else if (dataString.startsWith('STARTTLS')) {
|
||||
socket.write('220 Ready to start TLS\r\n');
|
||||
this.startTLS(socket);
|
||||
} else if (dataString.startsWith('DATA')) {
|
||||
socket.write('354 End data with <CR><LF>.<CR><LF>\r\n');
|
||||
let emailBuffer = this.emailBufferStringMap.get(socket);
|
||||
if (!emailBuffer) {
|
||||
this.emailBufferStringMap.set(socket, '');
|
||||
}
|
||||
} else if (dataString.startsWith('QUIT')) {
|
||||
socket.write('221 Bye\r\n');
|
||||
console.log('Received QUIT command, closing the socket...');
|
||||
socket.destroy();
|
||||
this.parseEmail(socket);
|
||||
} else {
|
||||
let emailBuffer = this.emailBufferStringMap.get(socket);
|
||||
if (typeof emailBuffer === 'string') {
|
||||
emailBuffer += dataString;
|
||||
this.emailBufferStringMap.set(socket, emailBuffer);
|
||||
}
|
||||
socket.write('250 Ok\r\n');
|
||||
}
|
||||
|
||||
if (dataString.endsWith('\r\n.\r\n') ) { // End of data
|
||||
console.log('Received end of data.');
|
||||
}
|
||||
}
|
||||
|
||||
private async parseEmail(socket: plugins.net.Socket | plugins.tls.TLSSocket) {
|
||||
let emailData = this.emailBufferStringMap.get(socket);
|
||||
// lets strip the end sequence
|
||||
emailData = emailData?.replace(/\r\n\.\r\n$/, '');
|
||||
|
||||
plugins.smartfile.fs.ensureDirSync(paths.receivedEmailsDir);
|
||||
plugins.smartfile.memory.toFsSync(emailData, plugins.path.join(paths.receivedEmailsDir, `${Date.now()}.eml`));
|
||||
|
||||
|
||||
if (!emailData) {
|
||||
console.error('No email data found for socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
let mightBeSpam = false;
|
||||
|
||||
// Verifying the email with DKIM
|
||||
try {
|
||||
const isVerified = await this.mtaRef.dkimVerifier.verify(emailData);
|
||||
mightBeSpam = !isVerified;
|
||||
} catch (error) {
|
||||
console.error('Failed to verify DKIM signature:', error);
|
||||
mightBeSpam = true;
|
||||
}
|
||||
|
||||
const parsedEmail = await plugins.mailparser.simpleParser(emailData);
|
||||
console.log(parsedEmail)
|
||||
const email = new Email({
|
||||
from: parsedEmail.from?.value[0].address || '',
|
||||
to:
|
||||
parsedEmail.to instanceof Array
|
||||
? parsedEmail.to[0].value[0].address
|
||||
: parsedEmail.to?.value[0].address,
|
||||
subject: parsedEmail.subject || '',
|
||||
text: parsedEmail.html || parsedEmail.text,
|
||||
attachments:
|
||||
parsedEmail.attachments?.map((attachment) => ({
|
||||
filename: attachment.filename || '',
|
||||
content: attachment.content,
|
||||
contentType: attachment.contentType,
|
||||
})) || [],
|
||||
mightBeSpam: mightBeSpam,
|
||||
});
|
||||
|
||||
console.log('mail received!');
|
||||
console.log(email);
|
||||
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.server.listen(this.smtpServerOptions.port, () => {
|
||||
console.log(`SMTP Server is now running on port ${this.smtpServerOptions.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.server.getConnections((err, count) => {
|
||||
if (err) throw err;
|
||||
console.log('Number of active connections: ', count);
|
||||
});
|
||||
this.server.close(() => {
|
||||
console.log('SMTP Server is now stopped');
|
||||
});
|
||||
}
|
||||
}
|
12
ts/paths.ts
Normal file
12
ts/paths.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
||||
export const assetsDir = plugins.path.join(packageDir, './assets');
|
||||
export const keysDir = plugins.path.join(assetsDir, './keys');
|
||||
export const dnsRecordsDir = plugins.path.join(assetsDir, './dns-records');
|
||||
export const sentEmailsDir = plugins.path.join(assetsDir, './sent-emails');
|
||||
export const receivedEmailsDir = plugins.path.join(assetsDir, './received-emails');
|
||||
plugins.smartfile.fs.ensureDirSync(keysDir);
|
@ -1,6 +0,0 @@
|
||||
import * as plugins from './platformservice.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../'
|
||||
);
|
@ -1,24 +0,0 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export {
|
||||
path
|
||||
}
|
||||
|
||||
// @api.global scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
|
||||
export {
|
||||
typedrequest,
|
||||
typedserver,
|
||||
}
|
||||
|
||||
// pushrocks scope
|
||||
// pushrocks scope
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
|
||||
export { projectinfo, qenv, smartdata, smartpath };
|
77
ts/plugins.ts
Normal file
77
ts/plugins.ts
Normal file
@ -0,0 +1,77 @@
|
||||
// node native
|
||||
import * as dns from 'dns';
|
||||
import * as fs from 'fs';
|
||||
import * as crypto from 'crypto';
|
||||
import * as net from 'net';
|
||||
import * as path from 'path';
|
||||
import * as tls from 'tls';
|
||||
import * as util from 'util';
|
||||
|
||||
export {
|
||||
dns,
|
||||
fs,
|
||||
crypto,
|
||||
net,
|
||||
path,
|
||||
tls,
|
||||
util,
|
||||
}
|
||||
|
||||
// @serve.zone scope
|
||||
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||
|
||||
export {
|
||||
servezoneInterfaces
|
||||
}
|
||||
|
||||
// @api.global scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
import * as typedsocket from '@api.global/typedsocket';
|
||||
|
||||
export {
|
||||
typedrequest,
|
||||
typedserver,
|
||||
typedsocket,
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartmail from '@push.rocks/smartmail';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartrx from '@push.rocks/smartrx';
|
||||
|
||||
export { projectinfo, qenv, smartdata, smartfile, smartlog, smartmail, smartpath, smartpromise, smartrequest, smartrx };
|
||||
|
||||
// apiclient.xyz scope
|
||||
import * as letterxpress from '@apiclient.xyz/letterxpress';
|
||||
|
||||
export {
|
||||
letterxpress,
|
||||
}
|
||||
|
||||
// tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export {
|
||||
tsclass,
|
||||
}
|
||||
|
||||
// third party
|
||||
import * as mailauth from 'mailauth';
|
||||
import { dkimSign } from 'mailauth/lib/dkim/sign.js';
|
||||
import mailparser from 'mailparser';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
export {
|
||||
mailauth,
|
||||
dkimSign,
|
||||
mailparser,
|
||||
uuid,
|
||||
}
|
1
ts/sms/index.ts
Normal file
1
ts/sms/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './smsservice.js';
|
89
ts/sms/smsservice.ts
Normal file
89
ts/sms/smsservice.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { SzPlatformService } from '../classes.platformservice.js';
|
||||
|
||||
export interface ISmsConstructorOptions {
|
||||
apiGatewayApiToken: string;
|
||||
}
|
||||
|
||||
export class SmsService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
public projectinfo: plugins.projectinfo.ProjectInfo;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public options: ISmsConstructorOptions;
|
||||
|
||||
constructor(platformServiceRefArg: SzPlatformService, optionsArg: ISmsConstructorOptions) {
|
||||
this.platformServiceRef = platformServiceRefArg;
|
||||
this.options = optionsArg;
|
||||
this.platformServiceRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the financeflow instance
|
||||
*/
|
||||
public async start() {
|
||||
logger.log('info', `starting sms service`);
|
||||
this.projectinfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.platformservice.sms.IRequest_SendSms>(
|
||||
'sendSms',
|
||||
async (reqData) => {
|
||||
await this.sendSms(reqData.toNumber, reqData.fromName, reqData.messageText);
|
||||
return {
|
||||
status: 'ok',
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.platformservice.sms.IRequest_SendVerificationCode>(
|
||||
'sendVerificationCode',
|
||||
async (reqData) => {
|
||||
const verificationCode = (
|
||||
await this.sendVerificationCode(reqData.toNumber, reqData.fromName)
|
||||
).toString();
|
||||
return {
|
||||
status: 'ok',
|
||||
verificationCode,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async sendSms(toNumber: number, fromName: string, messageText: string) {
|
||||
const payload = {
|
||||
sender: fromName,
|
||||
message: messageText,
|
||||
recipients: [{ msisdn: toNumber }],
|
||||
};
|
||||
|
||||
const resp = await plugins.smartrequest.request('https://gatewayapi.com/rest/mtsms', {
|
||||
method: 'POST',
|
||||
requestBody: JSON.stringify(payload),
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${this.options.apiGatewayApiToken}:`).toString('base64')}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const json = await resp.body;
|
||||
logger.log('info', `sent an sms to ${toNumber} with text '${messageText}'`, {
|
||||
eventType: 'sentSms',
|
||||
sms: {
|
||||
fromName: fromName,
|
||||
toNumber: toNumber.toString(),
|
||||
messageText: messageText,
|
||||
},
|
||||
});
|
||||
console.log(JSON.stringify(json, null, 2));
|
||||
}
|
||||
|
||||
public async sendVerificationCode(toNumber: number, fromName: string) {
|
||||
let verificationCode = Math.floor(100000 + Math.random() * 900000);
|
||||
await this.sendSms(toNumber, fromName, `Your verification code: ${verificationCode}`);
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
}
|
Reference in New Issue
Block a user