300 lines
8.6 KiB
PHP
300 lines
8.6 KiB
PHP
<?php
|
|
$mapType = function (?string $type): string {
|
|
if (!is_string($type) || $type === '') {
|
|
return 'unknown';
|
|
}
|
|
|
|
$type = strtolower(ltrim($type, '\\'));
|
|
|
|
return match ($type) {
|
|
'int', 'integer', 'float', 'double' => 'number',
|
|
'string' => 'string',
|
|
'bool', 'boolean' => 'boolean',
|
|
'array', 'iterable' => 'unknown[]',
|
|
'object', 'stdclass' => 'Record<string, unknown>',
|
|
'mixed', 'resource' => 'unknown',
|
|
'null', 'void' => 'null',
|
|
'scalar' => 'string | number | boolean',
|
|
'callable', 'closure' => '(...args: unknown[]) => unknown',
|
|
'never' => 'never',
|
|
default => 'unknown',
|
|
};
|
|
};
|
|
|
|
$mapUnionType = function (mixed $type) use ($mapType): string {
|
|
if (is_array($type)) {
|
|
$parts = array();
|
|
foreach ($type as $singleType) {
|
|
$parts[] = $mapType(is_string($singleType) ? $singleType : null);
|
|
}
|
|
$parts = array_values(array_unique($parts));
|
|
return empty($parts) ? 'unknown' : implode(' | ', $parts);
|
|
}
|
|
|
|
return $mapType(is_string($type) ? $type : null);
|
|
};
|
|
|
|
$normalizeTypeStructures = function (mixed $structure): array {
|
|
if (!is_array($structure)) {
|
|
return array();
|
|
}
|
|
if (array_key_exists('type', $structure) && array_key_exists('properties', $structure)) {
|
|
return array($structure);
|
|
}
|
|
return array_values(array_filter($structure, 'is_array'));
|
|
};
|
|
|
|
$findTypeStructure = function (?string $type, mixed $structure) use ($normalizeTypeStructures): array|null {
|
|
if (!is_string($type) || $type === '') {
|
|
return null;
|
|
}
|
|
$type = ltrim($type, '\\');
|
|
foreach ($normalizeTypeStructures($structure) as $one_structure) {
|
|
if (($one_structure['type'] ?? null) === $type) {
|
|
return $one_structure;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
$mapTypeWithStructure = null;
|
|
$mapUnionTypeWithStructure = null;
|
|
|
|
$mapTypeWithStructure = function (?string $type, mixed $structure) use ($mapType, &$mapUnionTypeWithStructure): string {
|
|
if (is_array($structure) && array_key_exists('type', $structure) && array_key_exists('properties', $structure)) {
|
|
$properties = array();
|
|
foreach (($structure['properties'] ?? array()) as $property) {
|
|
if (!is_array($property) || !isset($property['name'])) {
|
|
continue;
|
|
}
|
|
$propertyType = $mapUnionTypeWithStructure($property['type'] ?? null, $property['type_structure'] ?? null);
|
|
if (!empty($property['nullable']) && strpos($propertyType, 'null') === false) {
|
|
$propertyType .= ' | null';
|
|
}
|
|
$properties[] = $property['name'] . ': ' . $propertyType;
|
|
}
|
|
return empty($properties) ? 'Record<string, unknown>' : '{ ' . implode('; ', $properties) . ' }';
|
|
}
|
|
|
|
return $mapType($type);
|
|
};
|
|
|
|
$mapUnionTypeWithStructure = function (mixed $type, mixed $structure) use ($mapType, $findTypeStructure, &$mapTypeWithStructure): string {
|
|
if (is_array($type)) {
|
|
$parts = array();
|
|
foreach ($type as $singleType) {
|
|
$typeName = is_string($singleType) ? $singleType : null;
|
|
$parts[] = $mapTypeWithStructure($typeName, $findTypeStructure($typeName, $structure));
|
|
}
|
|
$parts = array_values(array_unique($parts));
|
|
return empty($parts) ? 'unknown' : implode(' | ', $parts);
|
|
}
|
|
|
|
return $mapTypeWithStructure(is_string($type) ? $type : null, $structure);
|
|
};
|
|
?>
|
|
/**
|
|
* Generated by APIlite
|
|
* https://gitea.tpsoft.org/TPsoft.org/APIlite
|
|
*
|
|
* <?php echo date('Y-m-d H:i:s'); ?>
|
|
*/
|
|
|
|
export interface APIliteActionResponse<T> {
|
|
status: 'OK';
|
|
data: T;
|
|
msg: string;
|
|
}
|
|
|
|
export interface APIliteErrorResponse {
|
|
status: 'ERROR';
|
|
msg: string;
|
|
}
|
|
|
|
export interface APIliteMethodParam {
|
|
name: string;
|
|
type: string | null;
|
|
optional: boolean;
|
|
default: unknown;
|
|
doc: string | null;
|
|
}
|
|
|
|
export interface APIliteMethodDoc {
|
|
name: string;
|
|
doc: string | null;
|
|
description: string | null;
|
|
params: APIliteMethodParam[];
|
|
return: string | string[] | null;
|
|
return_structure: APIliteTypeStructure | APIliteTypeStructure[] | null;
|
|
}
|
|
|
|
export interface APIliteTypeStructureProperty {
|
|
name: string;
|
|
type: string | string[] | null;
|
|
nullable: boolean;
|
|
type_structure: APIliteTypeStructure | APIliteTypeStructure[] | null;
|
|
}
|
|
|
|
export interface APIliteTypeStructure {
|
|
type: string;
|
|
recursive?: boolean;
|
|
properties: APIliteTypeStructureProperty[];
|
|
}
|
|
|
|
export interface APIliteHelpResponse {
|
|
name: string;
|
|
html_version: string;
|
|
javascript_version: string;
|
|
typescript_version: string;
|
|
actions: APIliteMethodDoc[];
|
|
status: string;
|
|
data: string;
|
|
msg: string;
|
|
}
|
|
|
|
type APIliteRequestHeaders = Partial<Record<string, string>>;
|
|
|
|
class <?php echo $this->apiName; ?> {
|
|
endpoint: string = <?php echo sprintf(substr($this->endpoint, 0, 4) == 'http' ? '"%s"' : '%s', $this->endpoint); ?>;
|
|
private readonly bearerStorageKey: string = 'apilite_bearer_token';
|
|
|
|
private normalizeBearerToken(token: string | null | undefined): string | null {
|
|
if (typeof token !== 'string') {
|
|
return null;
|
|
}
|
|
const normalizedToken = token.trim();
|
|
return normalizedToken === '' ? null : normalizedToken;
|
|
}
|
|
|
|
private getStorage(): Storage | null {
|
|
try {
|
|
if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
|
|
return window.localStorage;
|
|
}
|
|
} catch {
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private getBearerToken(): string | null {
|
|
const storage = this.getStorage();
|
|
if (storage === null) {
|
|
return null;
|
|
}
|
|
return this.normalizeBearerToken(storage.getItem(this.bearerStorageKey));
|
|
}
|
|
|
|
private getRequestHeaders(headers: APIliteRequestHeaders = {}): APIliteRequestHeaders {
|
|
const requestHeaders: APIliteRequestHeaders = { ...headers };
|
|
if (typeof requestHeaders.Authorization === 'undefined') {
|
|
const token = this.getBearerToken();
|
|
if (token !== null) {
|
|
requestHeaders.Authorization = `Bearer ${token}`;
|
|
}
|
|
}
|
|
return requestHeaders;
|
|
}
|
|
|
|
private applyHeaders(xhttp: XMLHttpRequest, headers: APIliteRequestHeaders): void {
|
|
Object.keys(headers).forEach((key) => {
|
|
const value = headers[key];
|
|
if (typeof value === 'undefined' || value === null) {
|
|
return;
|
|
}
|
|
xhttp.setRequestHeader(key, value);
|
|
});
|
|
}
|
|
|
|
bearerSet(token: string | null): void {
|
|
const storage = this.getStorage();
|
|
if (storage === null) {
|
|
return;
|
|
}
|
|
const normalizedToken = this.normalizeBearerToken(token);
|
|
if (normalizedToken === null) {
|
|
storage.removeItem(this.bearerStorageKey);
|
|
return;
|
|
}
|
|
storage.setItem(this.bearerStorageKey, normalizedToken);
|
|
}
|
|
|
|
private call(
|
|
method: string,
|
|
data: Record<string, unknown>,
|
|
callback: (response: APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse) => void
|
|
): void {
|
|
const xhttp = new XMLHttpRequest();
|
|
const headers = this.getRequestHeaders();
|
|
xhttp.withCredentials = true;
|
|
xhttp.onreadystatechange = function() {
|
|
if (this.readyState === 4) {
|
|
if (this.status === 200) {
|
|
const response = JSON.parse(this.responseText) as APIliteHelpResponse | APIliteActionResponse<unknown> | APIliteErrorResponse;
|
|
callback(response);
|
|
} else {
|
|
callback({ status: 'ERROR', msg: 'HTTP STATUS ' + this.status });
|
|
}
|
|
}
|
|
};
|
|
|
|
const formData = new FormData();
|
|
Object.keys(data).forEach((key) => {
|
|
const rawValue = data[key];
|
|
if (typeof rawValue === 'undefined') {
|
|
return;
|
|
}
|
|
let value: string | Blob;
|
|
if (rawValue instanceof Blob) {
|
|
value = rawValue;
|
|
} else if (typeof rawValue === 'object' && rawValue !== null) {
|
|
value = JSON.stringify(rawValue);
|
|
} else {
|
|
value = String(rawValue);
|
|
}
|
|
formData.append(key, value);
|
|
});
|
|
|
|
xhttp.open('POST', this.endpoint + '?action=' + method);
|
|
this.applyHeaders(xhttp, headers);
|
|
xhttp.send(formData);
|
|
}
|
|
|
|
private callPromise<T>(method: string, data: Record<string, unknown>): Promise<T> {
|
|
return new Promise<T>((resolve, reject) => {
|
|
this.call(method, data, (response) => {
|
|
if (method === '__HELP__') {
|
|
resolve(response as T);
|
|
return;
|
|
}
|
|
if (response.status === 'OK') {
|
|
resolve(response.data as T);
|
|
return;
|
|
}
|
|
reject(response.msg);
|
|
});
|
|
});
|
|
}
|
|
|
|
help(): Promise<APIliteHelpResponse> {
|
|
return this.callPromise<APIliteHelpResponse>('__HELP__', {});
|
|
}
|
|
|
|
<?php if (is_array($this->methods)) foreach ($this->methods as $method) {
|
|
$paramsSignature = array();
|
|
$paramsPayload = array();
|
|
foreach ($method['params'] as $param) {
|
|
$paramType = $mapType($param['type']);
|
|
$paramsSignature[] = $param['name'] . ($param['optional'] ? '?' : '') . ': ' . $paramType;
|
|
$paramsPayload[] = $param['name'];
|
|
}
|
|
$returnType = $mapUnionTypeWithStructure($method['return'], $method['return_structure'] ?? null);
|
|
?>
|
|
<?php echo $method['name']; ?>(<?php echo implode(', ', $paramsSignature); ?>): Promise<APIliteActionResponse<<?php echo $returnType; ?>>> {
|
|
return this.callPromise<APIliteActionResponse<<?php echo $returnType; ?>>>('<?php echo $method['name']; ?>', { <?php echo implode(', ', $paramsPayload); ?> });
|
|
}
|
|
|
|
<?php } ?>
|
|
}
|
|
|
|
export default new <?php echo $this->apiName; ?>();
|