Compare commits
15 Commits
v9.2.1
...
2886ab6522
| Author | SHA1 | Date | |
|---|---|---|---|
| 2886ab6522 | |||
| 994cc72e1d | |||
| a02ce50cf3 | |||
| c16c07ea42 | |||
| 535cf6ea05 | |||
| 766ed86999 | |||
| 76a2bf0e88 | |||
| fe8fcdf45b | |||
| 7a99b1ab55 | |||
| 41f6e640e1 | |||
| 26f1902996 | |||
| c8b8199e93 | |||
| 4a81cbf321 | |||
| b1d846de32 | |||
| 748a8740eb |
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,4 +1,24 @@
|
|||||||
node_modules
|
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode/*
|
||||||
dist
|
|
||||||
|
client/node_modules
|
||||||
|
client/dist
|
||||||
|
|
||||||
|
.vs/
|
||||||
|
.user
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|||||||
1536
3CX_TAPI.user.js
1536
3CX_TAPI.user.js
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,9 @@ module.exports = {
|
|||||||
namespace: 'http://cp-solutions.at',
|
namespace: 'http://cp-solutions.at',
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
author: pkg.author,
|
author: pkg.author,
|
||||||
copyright: 'Copyright 2021 CP Solutions GmbH',
|
copyright: 'Copyright CP Solutions GmbH',
|
||||||
source: pkg.repository.url,
|
source: pkg.repository.url,
|
||||||
downloadURL: 'https://source.cp-austria.at/git/CPATRD/3cx_tapi/raw/branch/master/3CX_TAPI.user.js',
|
downloadURL: `${pkg.repository.url}/raw/branch/master/3CX_TAPI.user.js`,
|
||||||
match: [
|
match: [
|
||||||
'https://192.168.0.154:5001/*',
|
'https://192.168.0.154:5001/*',
|
||||||
'https://cpsolution.my3cx.at:5001/*'
|
'https://cpsolution.my3cx.at:5001/*'
|
||||||
@@ -22,7 +22,7 @@ module.exports = {
|
|||||||
'GM.setValue'
|
'GM.setValue'
|
||||||
],
|
],
|
||||||
connect: [
|
connect: [
|
||||||
'cpatapi.cpsrvweb2016.cp-austria.at'
|
'3cxtapi.cp-austria.at'
|
||||||
],
|
],
|
||||||
'run-at': 'document-end'
|
'run-at': 'document-end'
|
||||||
}
|
}
|
||||||
4097
package-lock.json → client/package-lock.json
generated
4097
package-lock.json → client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "3cx-tapi",
|
"name": "3cx-tapi",
|
||||||
"description": "3CX CP Tapi and Projectmanager integration",
|
"description": "3CX CP Tapi and Projectmanager integration",
|
||||||
"version": "9.2.1",
|
"version": "9.3.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Daniel Triendl",
|
"name": "Daniel Triendl",
|
||||||
"email": "d.triendl@cp-solutions.at"
|
"email": "d.triendl@cp-solutions.at"
|
||||||
@@ -20,34 +20,35 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://source.cp-austria.at/git/CPATRD/3cx_tapi.git"
|
"url": "https://source.cp-austria.at/CPATRD/3cx_tapi.git"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chrono-node": "^2.7.7"
|
"@trim21/gm-fetch": "^0.3.0",
|
||||||
|
"chrono-node": "^2.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.29.0",
|
||||||
|
"@babel/preset-env": "^7.29.2",
|
||||||
"@types/greasemonkey": "^4.0.7",
|
"@types/greasemonkey": "^4.0.7",
|
||||||
"@babel/core": "^7.25.8",
|
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
||||||
"@babel/preset-env": "^7.25.8",
|
"@typescript-eslint/parser": "^8.57.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
"babel-loader": "^10.1.1",
|
||||||
"@typescript-eslint/parser": "^8.8.1",
|
|
||||||
"babel-loader": "^9.2.1",
|
|
||||||
"browserslist": "^4.21.9",
|
"browserslist": "^4.21.9",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.4",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-promise": "^7.1.0S",
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
"less": "4.2.0",
|
"less": "^4.6.4",
|
||||||
"less-loader": "^12.2.0",
|
"less-loader": "^12.3.2",
|
||||||
"style-loader": "^4.0.0",
|
"style-loader": "^4.0.0",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.4",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.9.3",
|
||||||
"userscript-metadata-webpack-plugin": "^0.4.0",
|
"userscript-metadata-webpack-plugin": "^0.4.2",
|
||||||
"webpack": "^5.95.0",
|
"webpack": "^5.105.4",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^5.2.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^7.0.2",
|
||||||
"webpack-livereload-plugin": "3.0.2",
|
"webpack-livereload-plugin": "3.0.2",
|
||||||
"webpack-merge": "^6.0.1"
|
"webpack-merge": "^6.0.1"
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ npm run build
|
|||||||
## distribution
|
## distribution
|
||||||
|
|
||||||
```
|
```
|
||||||
cp "dist/3CX TAPI.prod.user.js" 3CX_TAPI.user.js
|
cp "dist/3CX TAPI.prod.user.js" ../3CX_TAPI.user.js
|
||||||
```
|
```
|
||||||
|
|
||||||
And commit 3CX_TAPI.user.js
|
And commit 3CX_TAPI.user.js
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as chrono from 'chrono-node'
|
import * as chrono from 'chrono-node'
|
||||||
import { TapiContact } from './tapi-contact'
|
import { TapiContact } from './tapi-contact'
|
||||||
import { extractNumber } from './utils'
|
import { extractNumber } from './utils'
|
||||||
import GM_fetch from './gm-fetch'
|
import GM_fetch from '@trim21/gm-fetch'
|
||||||
|
import { Config } from './config'
|
||||||
|
|
||||||
export class CallHistory {
|
export class CallHistory {
|
||||||
private callerIds: { [number: string]: TapiContact } = {}
|
private callerIds: { [number: string]: TapiContact } = {}
|
||||||
@@ -86,7 +87,7 @@ export class CallHistory {
|
|||||||
if (this.callerIds[number] !== undefined) {
|
if (this.callerIds[number] !== undefined) {
|
||||||
this.updateCallHistoryEntry(element, this.callerIds[number])
|
this.updateCallHistoryEntry(element, this.callerIds[number])
|
||||||
} else {
|
} else {
|
||||||
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
|
var response = await GM_fetch(Config.tapi_server_url + '/callerid/' + encodeURIComponent(number))
|
||||||
var callerId: TapiContact = { tD_NAME: '' }
|
var callerId: TapiContact = { tD_NAME: '' }
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
callerId = await response.json() as TapiContact
|
callerId = await response.json() as TapiContact
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import GM_fetch from './gm-fetch'
|
import GM_fetch from '@trim21/gm-fetch'
|
||||||
import { TapiContact } from './tapi-contact'
|
import { TapiContact } from './tapi-contact'
|
||||||
import { extractNumber } from './utils'
|
import { extractNumber } from './utils'
|
||||||
|
import { Config } from './config'
|
||||||
|
|
||||||
export class CallNotification {
|
export class CallNotification {
|
||||||
public async showCallNotification (element: HTMLElement) {
|
public async showCallNotification (element: HTMLElement) {
|
||||||
@@ -14,7 +15,7 @@ export class CallNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('TAPI searching callerid for', number)
|
console.log('TAPI searching callerid for', number)
|
||||||
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/callerid/' + encodeURIComponent(number))
|
var response = await GM_fetch(Config.tapi_server_url + '/callerid/' + encodeURIComponent(number))
|
||||||
console.log('TAPI callerid response', response)
|
console.log('TAPI callerid response', response)
|
||||||
var notification = {
|
var notification = {
|
||||||
text: number
|
text: number
|
||||||
5
client/src/config.ts
Normal file
5
client/src/config.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class _Config {
|
||||||
|
public tapi_server_url: string = 'https://3cxtapi.cp-austria.at'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Config = new _Config()
|
||||||
@@ -22,3 +22,5 @@ waitForKeyElements('.call-history-list call', (element) => { callHistory.showCal
|
|||||||
const status = new Status()
|
const status = new Status()
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
waitForKeyElements('wc-account-menu', (element) => { status.showStatus(element) }, false)
|
waitForKeyElements('wc-account-menu', (element) => { status.showStatus(element) }, false)
|
||||||
|
|
||||||
|
waitForKeyElements('wc-account-menu i.status-indicator', (element) => { status.watchStatus(element) }, false)
|
||||||
@@ -2,7 +2,8 @@ import './search.css'
|
|||||||
import { TapiContact } from './tapi-contact'
|
import { TapiContact } from './tapi-contact'
|
||||||
import { debounce } from './debounce'
|
import { debounce } from './debounce'
|
||||||
import { fireChangeEvents } from './utils'
|
import { fireChangeEvents } from './utils'
|
||||||
import GM_fetch from './gm-fetch'
|
import GM_fetch from '@trim21/gm-fetch'
|
||||||
|
import { Config } from './config'
|
||||||
|
|
||||||
export class Search {
|
export class Search {
|
||||||
private currentSearchText = ''
|
private currentSearchText = ''
|
||||||
@@ -103,7 +104,7 @@ export class Search {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('Searching TAPI')
|
console.log('Searching TAPI')
|
||||||
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/search?query=' + encodeURIComponent(searchText))
|
var response = await GM_fetch(Config.tapi_server_url + '/search?query=' + encodeURIComponent(searchText))
|
||||||
console.log('TAPI Search response', response)
|
console.log('TAPI Search response', response)
|
||||||
var contacts = await response.json() as TapiContact[]
|
var contacts = await response.json() as TapiContact[]
|
||||||
console.log('TAPI Contacts', contacts)
|
console.log('TAPI Contacts', contacts)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import GM_fetch from './gm-fetch';
|
import { Config } from './config';
|
||||||
import './status.css';
|
import './status.css';
|
||||||
import { ZcStatus } from './zc-status';
|
import { ZcStatus } from './zc-status';
|
||||||
|
import GM_fetch from "@trim21/gm-fetch";
|
||||||
|
|
||||||
declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any;
|
declare function waitForKeyElements(selectorOrFunction: any, callback: any, waitOnce: boolean): any;
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ export class Status {
|
|||||||
private async checkStatus() {
|
private async checkStatus() {
|
||||||
if (this._enabled) {
|
if (this._enabled) {
|
||||||
try {
|
try {
|
||||||
var response = await GM_fetch('http://cpatapi.cpsrvweb2016.cp-austria.at/availability/' + encodeURIComponent(this._user));
|
var response = await GM_fetch(Config.tapi_server_url + '/availability/' + encodeURIComponent(this._user));
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
var status = await response.json() as ZcStatus;
|
var status = await response.json() as ZcStatus;
|
||||||
if (this._currentStatus !== status.loggedIn) {
|
if (this._currentStatus !== status.loggedIn) {
|
||||||
@@ -156,4 +157,34 @@ export class Status {
|
|||||||
this._currentStatus = undefined;
|
this._currentStatus = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public watchStatus(element: HTMLElement) {
|
||||||
|
console.log('updateFavicon watching', element);
|
||||||
|
const attrObserver = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.attributeName === 'class') {
|
||||||
|
this.updateStatus(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
attrObserver.observe(element, { attributes: true });
|
||||||
|
this.updateStatus(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateStatus(element: HTMLElement) {
|
||||||
|
console.log('Status changed', element.classList);
|
||||||
|
var color = window.getComputedStyle(element).getPropertyValue("background-color");
|
||||||
|
console.log('Background color:', color);
|
||||||
|
this.setFaviconColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFaviconColor(color: string) {
|
||||||
|
var link = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
|
||||||
|
if (!link) {
|
||||||
|
link = document.createElement('link');
|
||||||
|
link.rel = 'icon';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
link.href = "data:image/svg+xml,%3Csvg width='32px' height='32px' xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cpolyline points='9,0 20,0 32,16 20,32 9,32, 20,16' fill='" + encodeURIComponent(color) + "' id='MyLine' /%3E%3C/svg%3E";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
30
server/.dockerignore
Normal file
30
server/.dockerignore
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
!**/.gitignore
|
||||||
|
!.git/HEAD
|
||||||
|
!.git/config
|
||||||
|
!.git/packed-refs
|
||||||
|
!.git/refs/heads/**
|
||||||
25
server/3cx_Tapi.sln
Normal file
25
server/3cx_Tapi.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 18
|
||||||
|
VisualStudioVersion = 18.5.11612.153 insiders
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CPATapi.Server", "src\CPATapi.Server\CPATapi.Server.csproj", "{7879A024-A074-FE67-0546-8668213BFA99}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{7879A024-A074-FE67-0546-8668213BFA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7879A024-A074-FE67-0546-8668213BFA99}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7879A024-A074-FE67-0546-8668213BFA99}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7879A024-A074-FE67-0546-8668213BFA99}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {E2CF5765-9038-4ED6-AC5C-1A3E569ABC6C}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
22
server/src/CPATapi.Server/CPATapi.Server.csproj
Normal file
22
server/src/CPATapi.Server/CPATapi.Server.csproj
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UserSecretsId>a7b40068-a2f6-4c63-bbd9-0fd346908fb0</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerfileContext>..\..</DockerfileContext>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.72" />
|
||||||
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Enrichers.ClientInfo" Version="2.9.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Dapper;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Controllers;
|
||||||
|
|
||||||
|
[Route("[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class AvailabilityController(IZeitConsensRepository zeitConsens) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[Route("users")]
|
||||||
|
[ProducesResponseType<IEnumerable<string>>(StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetUsers()
|
||||||
|
{
|
||||||
|
return Ok(await zeitConsens.GetUsersAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("{user}")]
|
||||||
|
[ProducesResponseType<Availability>(StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetAvailability(string user)
|
||||||
|
{
|
||||||
|
var stampCount = (await zeitConsens.GetStampsAsync(user, DateTime.Now.Date, DateTime.Now.AddDays(1).Date)).Count();
|
||||||
|
return Ok(new Availability { User = user, LoggedIn = stampCount % 2 != 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
30
server/src/CPATapi.Server/Controllers/CallerIdController.cs
Normal file
30
server/src/CPATapi.Server/Controllers/CallerIdController.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Controllers;
|
||||||
|
|
||||||
|
[Route("[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class CallerIdController(ITapiDirectoryRepository tapiDirectory) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[Route("{number}")]
|
||||||
|
[ProducesResponseType<TapiContact>(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> CallerIdAsync([FromRoute] string number)
|
||||||
|
{
|
||||||
|
if (number.Length >= 5)
|
||||||
|
{
|
||||||
|
var minMatch = number[^5..];
|
||||||
|
number = "%" + minMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await tapiDirectory.SearchByNumberAsync(number);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
server/src/CPATapi.Server/Controllers/ContactController.cs
Normal file
21
server/src/CPATapi.Server/Controllers/ContactController.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class ContactController(ILogger<ContactController> logger, ITapiDirectoryRepository tapiDirectory) : ControllerBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly ILogger<ContactController> _logger = logger;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Produces("application/json")]
|
||||||
|
[ProducesResponseType<IEnumerable<TapiContact>>(StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetAsync()
|
||||||
|
{
|
||||||
|
return Ok(await tapiDirectory.GetAllAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
25
server/src/CPATapi.Server/Controllers/SearchController.cs
Normal file
25
server/src/CPATapi.Server/Controllers/SearchController.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Controllers;
|
||||||
|
|
||||||
|
[Route("[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class SearchController(ITapiDirectoryRepository tapiDirectory) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType<IEnumerable<TapiContact>>(StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> SearchAsync([FromQuery] string query)
|
||||||
|
{
|
||||||
|
if (query == null)
|
||||||
|
{
|
||||||
|
return Ok(new List<TapiContact>());
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = Regex.Split(query, "\\s").Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim()).ToArray();
|
||||||
|
|
||||||
|
return Ok(await tapiDirectory.SearchAsync(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
28
server/src/CPATapi.Server/Dockerfile
Normal file
28
server/src/CPATapi.Server/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
# This stage is used when running from VS in fast mode (Default for Debug configuration)
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||||
|
USER $APP_UID
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# This stage is used to build the service project
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["CPATapi.Server/CPATapi.Server.csproj", "CPATapi.Server/"]
|
||||||
|
RUN dotnet restore "CPATapi.Server/CPATapi.Server.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/CPATapi.Server"
|
||||||
|
RUN dotnet build "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
# This stage is used to publish the service project to be copied to the final stage
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "CPATapi.Server.dll"]
|
||||||
5
server/src/CPATapi.Server/Interfaces/IRepository.cs
Normal file
5
server/src/CPATapi.Server/Interfaces/IRepository.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace CPATapi.Server.Interfaces;
|
||||||
|
|
||||||
|
public interface IRepository
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using CPATapi.Server.Models;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Interfaces;
|
||||||
|
|
||||||
|
public interface ITapiDirectoryRepository : IRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<TapiContact>> SearchAsync(string[] args);
|
||||||
|
Task<TapiContact?> SearchByNumberAsync(string number);
|
||||||
|
Task<IEnumerable<TapiContact>> GetAllAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using CPATapi.Server.Models;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Interfaces;
|
||||||
|
|
||||||
|
public interface IZeitConsensRepository : IRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<string>> GetUsersAsync();
|
||||||
|
Task<IEnumerable<Stamp>> GetStampsAsync(string user, DateTime from, DateTime to);
|
||||||
|
}
|
||||||
11
server/src/CPATapi.Server/Models/Availability.cs
Normal file
11
server/src/CPATapi.Server/Models/Availability.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Models;
|
||||||
|
|
||||||
|
public class Availability
|
||||||
|
{
|
||||||
|
[JsonPropertyName("user")]
|
||||||
|
public string User { get; set; }
|
||||||
|
[JsonPropertyName("loggedIn")]
|
||||||
|
public bool LoggedIn { get; set; }
|
||||||
|
}
|
||||||
7
server/src/CPATapi.Server/Models/Stamp.cs
Normal file
7
server/src/CPATapi.Server/Models/Stamp.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace CPATapi.Server.Models;
|
||||||
|
|
||||||
|
public class Stamp
|
||||||
|
{
|
||||||
|
public int MA_NR { get; set; }
|
||||||
|
public DateTime BU_BU { get; set; }
|
||||||
|
}
|
||||||
10
server/src/CPATapi.Server/Models/TapiContact.cs
Normal file
10
server/src/CPATapi.Server/Models/TapiContact.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace CPATapi.Server.Models;
|
||||||
|
|
||||||
|
public class TapiContact
|
||||||
|
{
|
||||||
|
public string TD_ID { get; set; }
|
||||||
|
public string TD_NAME { get; set; }
|
||||||
|
public string TD_NUMBER { get; set; }
|
||||||
|
public string TD_NUMBER_TAPI { get; set; }
|
||||||
|
public string TD_MEDIUM { get; set; }
|
||||||
|
}
|
||||||
35
server/src/CPATapi.Server/Program.cs
Normal file
35
server/src/CPATapi.Server/Program.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Repository;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Services.AddSerilog(config =>
|
||||||
|
{
|
||||||
|
config
|
||||||
|
.ReadFrom.Configuration(builder.Configuration)
|
||||||
|
.Enrich.WithClientIp()
|
||||||
|
.Enrich.WithCorrelationId()
|
||||||
|
.Enrich.WithRequestHeader("User-Agent")
|
||||||
|
.WriteTo.Console();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddTransient<ITapiDirectoryRepository, TapiDirectoryRepository>();
|
||||||
|
builder.Services.AddTransient<IZeitConsensRepository, ZeitConsensRepository>();
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseSerilogRequestLogging();
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
await app.RunAsync();
|
||||||
40
server/src/CPATapi.Server/Properties/launchSettings.json
Normal file
40
server/src/CPATapi.Server/Properties/launchSettings.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "contact",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CPATapiServer": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "availability/users",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "https://3cxtapi.cp-austria.at:5001;https://localhost:5001;http://localhost:5000"
|
||||||
|
},
|
||||||
|
"Container (Dockerfile)": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/contact",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true,
|
||||||
|
"useSSL": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:62406",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
server/src/CPATapi.Server/Repository/Repository.cs
Normal file
21
server/src/CPATapi.Server/Repository/Repository.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Data.Common;
|
||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Repository;
|
||||||
|
|
||||||
|
public class Repository: IRepository
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
|
||||||
|
protected Repository(IConfiguration config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
protected async Task<DbConnection> OpenAsync(string name)
|
||||||
|
{
|
||||||
|
var db = new SqlConnection(_config.GetConnectionString(name));
|
||||||
|
await db.OpenAsync();
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Text;
|
||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Models;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Repository;
|
||||||
|
|
||||||
|
internal class TapiDirectoryRepository(IConfiguration config) : Repository(config), ITapiDirectoryRepository
|
||||||
|
{
|
||||||
|
public async Task<IEnumerable<TapiContact>> SearchAsync(string[] args)
|
||||||
|
{
|
||||||
|
await using var con = await OpenAsync("Tapi");
|
||||||
|
var sql = new StringBuilder("""
|
||||||
|
SELECT TOP 10
|
||||||
|
TD_ID,
|
||||||
|
TD_NAME,
|
||||||
|
TD_NUMBER,
|
||||||
|
TD_NUMBER_TAPI,
|
||||||
|
TD_MEDIUM
|
||||||
|
FROM dbo.CP_TAPI_DIRECTORY
|
||||||
|
WHERE
|
||||||
|
""");
|
||||||
|
var first = true;
|
||||||
|
var dp = new DynamicParameters();
|
||||||
|
for (var i = 1; i <= args.Length; i++)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
sql.AppendLine("AND");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
sql.AppendLine($"(TD_NAME LIKE @s{i} OR");
|
||||||
|
sql.AppendLine($" TD_NUMBER_TAPI LIKE @s{i})");
|
||||||
|
dp.Add($"s{i}", $"%{args[i-1]}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await con.QueryAsync<TapiContact>(sql.ToString(), dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TapiContact?> SearchByNumberAsync(string number)
|
||||||
|
{
|
||||||
|
await using var con = await OpenAsync("Tapi");
|
||||||
|
|
||||||
|
var result = await con.QueryFirstOrDefaultAsync<TapiContact>("""
|
||||||
|
SELECT
|
||||||
|
TD_ID
|
||||||
|
,TD_NAME
|
||||||
|
,TD_NUMBER
|
||||||
|
,TD_NUMBER_TAPI
|
||||||
|
,TD_MEDIUM
|
||||||
|
FROM dbo.CP_TAPI_DIRECTORY
|
||||||
|
WHERE TD_NUMBER_TAPI LIKE @number
|
||||||
|
""", new {number});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<TapiContact>> GetAllAsync()
|
||||||
|
{
|
||||||
|
await using var con = await OpenAsync("Tapi");
|
||||||
|
|
||||||
|
return await con.QueryAsync<TapiContact>("""
|
||||||
|
SELECT
|
||||||
|
TD_ID
|
||||||
|
,TD_NAME
|
||||||
|
,TD_NUMBER
|
||||||
|
,TD_NUMBER_TAPI
|
||||||
|
,TD_MEDIUM
|
||||||
|
FROM dbo.CP_TAPI_DIRECTORY
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using CPATapi.Server.Interfaces;
|
||||||
|
using CPATapi.Server.Models;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace CPATapi.Server.Repository;
|
||||||
|
|
||||||
|
internal class ZeitConsensRepository(IConfiguration config) : Repository(config), IZeitConsensRepository
|
||||||
|
{
|
||||||
|
public async Task<IEnumerable<string>> GetUsersAsync()
|
||||||
|
{
|
||||||
|
await using var con = await OpenAsync("ZeitConsens");
|
||||||
|
return await con.QueryAsync<string>("""
|
||||||
|
SELECT DISTINCT MA_USER_NAME
|
||||||
|
FROM dbo.MA_DATEN
|
||||||
|
WHERE MA_USER_NAME IS NOT NULL AND MA_USER_AKTIV = 1
|
||||||
|
ORDER BY MA_USER_NAME
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Stamp>> GetStampsAsync(string user, DateTime from, DateTime to)
|
||||||
|
{
|
||||||
|
await using var con = await OpenAsync("ZeitConsens");
|
||||||
|
return await con.QueryAsync<Stamp>("""
|
||||||
|
SELECT
|
||||||
|
MA_NR
|
||||||
|
,BU_BU
|
||||||
|
FROM dbo.BU
|
||||||
|
INNER JOIN dbo.MA_DATEN on MA_NR = BU_MA_NR
|
||||||
|
WHERE
|
||||||
|
MA_USER_NAME = @user AND
|
||||||
|
BU_BU >= @from AND BU_BU < @to
|
||||||
|
""", new { user, from, to });
|
||||||
|
}
|
||||||
|
}
|
||||||
16
server/src/CPATapi.Server/appsettings.json
Normal file
16
server/src/CPATapi.Server/appsettings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"Serilog": {
|
||||||
|
"MinimumLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Override": {
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Tapi": "",
|
||||||
|
"ZeitConsens": ""
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
19
server/src/CPATapi.Server/publish.bat
Normal file
19
server/src/CPATapi.Server/publish.bat
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal
|
||||||
|
cd /d %~dp0
|
||||||
|
|
||||||
|
docker build -t source.cp-austria.at/cpatrd/3cx_tapi:latest -f Dockerfile ..
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo ERROR: Docker build failed!
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SET /P AREYOUSURE=Publish to source.cp-austria.at? (Y/[N])
|
||||||
|
IF /I "%AREYOUSURE%" NEQ "Y" GOTO END
|
||||||
|
|
||||||
|
docker push source.cp-austria.at/cpatrd/3cx_tapi:latest
|
||||||
|
|
||||||
|
:END
|
||||||
|
endlocal
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { parseGMResponse } from "./utils";
|
|
||||||
|
|
||||||
export default async function GM_fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
|
||||||
const request = new Request(input, init);
|
|
||||||
|
|
||||||
let data: string | undefined;
|
|
||||||
if (init?.body) {
|
|
||||||
data = await request.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
return await XHR(request, init, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function XHR(request: Request, init: RequestInit | undefined, data: string | undefined): Promise<Response> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (request.signal && request.signal.aborted) {
|
|
||||||
return reject(new DOMException("Aborted", "AbortError"));
|
|
||||||
}
|
|
||||||
|
|
||||||
GM.xmlHttpRequest({
|
|
||||||
url: request.url,
|
|
||||||
method: gmXHRMethod(request.method.toUpperCase()),
|
|
||||||
headers: Object.fromEntries(new Headers(init?.headers).entries()),
|
|
||||||
data: data,
|
|
||||||
responseType: "blob",
|
|
||||||
onload(res) {
|
|
||||||
resolve(parseGMResponse(request, res));
|
|
||||||
},
|
|
||||||
onabort() {
|
|
||||||
reject(new DOMException("Aborted", "AbortError"));
|
|
||||||
},
|
|
||||||
ontimeout() {
|
|
||||||
reject(new TypeError("Network request failed, timeout"));
|
|
||||||
},
|
|
||||||
onerror(err) {
|
|
||||||
reject(new TypeError("Failed to fetch: " + err.finalUrl));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "TRACE", "OPTIONS", "CONNECT"] as const;
|
|
||||||
|
|
||||||
// a ts type helper to narrow type
|
|
||||||
function includes<T extends U, U>(array: ReadonlyArray<T>, element: U): element is T {
|
|
||||||
return array.includes(element as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
function gmXHRMethod(method: string): (typeof httpMethods)[number] {
|
|
||||||
if (includes(httpMethods, method)) {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`unsupported http method ${method}`);
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
export function parseRawHeaders(h: string): Headers {
|
|
||||||
const s = h.trim();
|
|
||||||
if (!s) {
|
|
||||||
return new Headers();
|
|
||||||
}
|
|
||||||
|
|
||||||
const array: [string, string][] = s.split("\r\n").map((value) => {
|
|
||||||
let s = value.split(":");
|
|
||||||
return [s[0].trim(), s[1].trim()];
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Headers(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseGMResponse(req: Request, res: GM.Response<any>): Response {
|
|
||||||
return new ResImpl(res.response as Blob, {
|
|
||||||
statusCode: res.status,
|
|
||||||
statusText: res.statusText,
|
|
||||||
headers: parseRawHeaders(res.responseHeaders),
|
|
||||||
finalUrl: res.finalUrl,
|
|
||||||
redirected: res.finalUrl === req.url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ResInit {
|
|
||||||
redirected: boolean;
|
|
||||||
headers: Headers;
|
|
||||||
statusCode: number;
|
|
||||||
statusText: string;
|
|
||||||
finalUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResImpl implements Response {
|
|
||||||
private _bodyUsed: boolean;
|
|
||||||
private readonly rawBody: Blob;
|
|
||||||
private readonly init: ResInit;
|
|
||||||
|
|
||||||
readonly body: ReadableStream<Uint8Array> | null;
|
|
||||||
readonly headers: Headers;
|
|
||||||
readonly redirected: boolean;
|
|
||||||
readonly status: number;
|
|
||||||
readonly statusText: string;
|
|
||||||
readonly type: ResponseType;
|
|
||||||
readonly url: string;
|
|
||||||
|
|
||||||
constructor(body: Blob, init: ResInit) {
|
|
||||||
this.rawBody = body;
|
|
||||||
this.init = init;
|
|
||||||
|
|
||||||
//this.body = toReadableStream(body);
|
|
||||||
const { headers, statusCode, statusText, finalUrl, redirected } = init;
|
|
||||||
this.headers = headers;
|
|
||||||
this.status = statusCode;
|
|
||||||
this.statusText = statusText;
|
|
||||||
this.url = finalUrl;
|
|
||||||
this.type = "basic";
|
|
||||||
this.redirected = redirected;
|
|
||||||
this._bodyUsed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get bodyUsed(): boolean {
|
|
||||||
return this._bodyUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ok(): boolean {
|
|
||||||
return this.status < 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayBuffer(): Promise<ArrayBuffer> {
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
throw new TypeError("Failed to execute 'arrayBuffer' on 'Response': body stream already read");
|
|
||||||
}
|
|
||||||
this._bodyUsed = true;
|
|
||||||
return this.rawBody.arrayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
blob(): Promise<Blob> {
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
throw new TypeError("Failed to execute 'blob' on 'Response': body stream already read");
|
|
||||||
}
|
|
||||||
this._bodyUsed = true;
|
|
||||||
// `slice` will use empty string as default value, so need to pass all arguments.
|
|
||||||
return Promise.resolve(this.rawBody.slice(0, this.rawBody.size, this.rawBody.type));
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): Response {
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
throw new TypeError("Failed to execute 'clone' on 'Response': body stream already read");
|
|
||||||
}
|
|
||||||
return new ResImpl(this.rawBody, this.init);
|
|
||||||
}
|
|
||||||
|
|
||||||
formData(): Promise<FormData> {
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
throw new TypeError("Failed to execute 'formData' on 'Response': body stream already read");
|
|
||||||
}
|
|
||||||
this._bodyUsed = true;
|
|
||||||
return this.rawBody.text().then(decode);
|
|
||||||
}
|
|
||||||
|
|
||||||
async json(): Promise<any> {
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
throw new TypeError("Failed to execute 'json' on 'Response': body stream already read");
|
|
||||||
}
|
|
||||||
this._bodyUsed = true;
|
|
||||||
return JSON.parse(await this.rawBody.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
text(): Promise<string> {
|
|
||||||
if (this.bodyUsed) {
|
|
||||||
throw new TypeError("Failed to execute 'text' on 'Response': body stream already read");
|
|
||||||
}
|
|
||||||
this._bodyUsed = true;
|
|
||||||
return this.rawBody.text();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function decode(body: string) {
|
|
||||||
const form = new FormData();
|
|
||||||
|
|
||||||
body
|
|
||||||
.trim()
|
|
||||||
.split("&")
|
|
||||||
.forEach(function (bytes) {
|
|
||||||
if (bytes) {
|
|
||||||
const split = bytes.split("=");
|
|
||||||
const name = split.shift()?.replace(/\+/g, " ");
|
|
||||||
const value = split.join("=").replace(/\+/g, " ");
|
|
||||||
form.append(decodeURIComponent(name!), decodeURIComponent(value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return form;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
function toReadableStream(value: Blob) {
|
|
||||||
return new ReadableStream({
|
|
||||||
start(controller) {
|
|
||||||
controller.enqueue(value);
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
Reference in New Issue
Block a user