@fett/synology-api 0.0.1-beta.0 → 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,2 +1,22 @@
1
1
  # synology-api
2
+
2
3
  nodejs synology api wrapper
4
+
5
+ ## Usage
6
+
7
+ ```js
8
+ import SynologyApi from '@fett/synology-api';
9
+
10
+ // Create a new instance
11
+ const synologyApi = new SynologyApi(
12
+ server: "https:192.168.1.1:5001",
13
+ username: "admin",
14
+ password: "password",
15
+ );
16
+
17
+ // Connect to the server
18
+ await synologyApi.connect();
19
+
20
+ ```
21
+
22
+ ## API
@@ -1,2 +1,4 @@
1
1
  export declare const SYNOLOGY_API_AUTH = "SYNO.API.Auth";
2
2
  export declare const SYNOLOGY_API_INFO = "SYNO.API.Info";
3
+ export declare const GLOBAL_QUICK_CONNECT_URL = "https://global.quickconnect.cn/Serv.php";
4
+ export declare const QUICK_CONNECT_PINGPANG_API = "/webman/pingpong.cgi?action=cors&quickconnect=true";
package/dist/constants.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export const SYNOLOGY_API_AUTH = "SYNO.API.Auth";
2
2
  export const SYNOLOGY_API_INFO = "SYNO.API.Info";
3
+ export const GLOBAL_QUICK_CONNECT_URL = "https://global.quickconnect.cn/Serv.php";
4
+ export const QUICK_CONNECT_PINGPANG_API = "/webman/pingpong.cgi?action=cors&quickconnect=true";
package/dist/core.d.ts CHANGED
@@ -27,7 +27,7 @@ export declare class SynologyApi extends BaseSynologyApi {
27
27
  constructor(options: SynologyApiOptions);
28
28
  connect(): Promise<boolean>;
29
29
  disconnect(): Promise<boolean>;
30
- _getApiInfo(): Promise<void>;
30
+ private _getApiInfo;
31
31
  hasApi(apiName: string): any;
32
32
  run(apiName: string, options: {
33
33
  method?: "get" | "post";
package/dist/core.js CHANGED
@@ -7,9 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ // reference: https://kb.synology.com/zh-tw/DSM/tutorial/What_websites_does_Synology_NAS_connect_to_when_running_services_or_updating_software
10
11
  import axios from "axios";
11
- import { SYNOLOGY_API_AUTH, SYNOLOGY_API_INFO } from "./constants";
12
12
  import { BaseSynologyApi } from "./modules";
13
+ import { isHttpUrl } from "./utils";
14
+ import { getServerInfo } from "./helpers";
15
+ import { login, logout, getApiInfo } from "./modules/Api";
13
16
  export class SynologyApi extends BaseSynologyApi {
14
17
  constructor(options) {
15
18
  super();
@@ -23,21 +26,15 @@ export class SynologyApi extends BaseSynologyApi {
23
26
  }
24
27
  connect() {
25
28
  return __awaiter(this, void 0, void 0, function* () {
26
- const params = {
27
- api: SYNOLOGY_API_AUTH,
28
- version: 6,
29
- method: "login",
30
- account: this.username,
31
- passwd: this.password,
32
- format: "sid",
33
- };
29
+ // if quickconnect id
30
+ if (!isHttpUrl(this.server)) {
31
+ this.server = yield getServerInfo(this.server);
32
+ // reset base url
33
+ this.baseUrl = `${this.server}/webapi/`;
34
+ }
34
35
  try {
35
- const url = `${this.baseUrl}entry.cgi`;
36
- const result = yield axios.get(url, { params });
37
- if (!result.data.success) {
38
- throw new Error(result.data.error.message);
39
- }
40
- this.authInfo = result.data.data;
36
+ const result = yield login(this);
37
+ this.authInfo = result.data;
41
38
  this.isConnecting = true;
42
39
  yield this._getApiInfo();
43
40
  return true;
@@ -50,17 +47,8 @@ export class SynologyApi extends BaseSynologyApi {
50
47
  }
51
48
  disconnect() {
52
49
  return __awaiter(this, void 0, void 0, function* () {
53
- const params = {
54
- api: SYNOLOGY_API_AUTH,
55
- version: 6,
56
- method: "logout",
57
- };
58
50
  try {
59
- const url = `${this.baseUrl}entry.cgi`;
60
- const result = yield axios.get(url, { params });
61
- if (!result.data.success) {
62
- throw new Error(result.data.error.message);
63
- }
51
+ yield logout(this);
64
52
  this.authInfo = null;
65
53
  this.apiInfo = {};
66
54
  this.isConnecting = false;
@@ -74,18 +62,9 @@ export class SynologyApi extends BaseSynologyApi {
74
62
  }
75
63
  _getApiInfo() {
76
64
  return __awaiter(this, void 0, void 0, function* () {
77
- const params = {
78
- api: SYNOLOGY_API_INFO,
79
- version: 1,
80
- method: "query",
81
- };
82
65
  try {
83
- const url = `${this.baseUrl}entry.cgi`;
84
- const result = yield axios.get(url, { params });
85
- if (!result.data.success) {
86
- throw new Error(result.data.error.message);
87
- }
88
- this.apiInfo = result.data.data;
66
+ const result = yield getApiInfo(this);
67
+ this.apiInfo = result.data;
89
68
  }
90
69
  catch (err) {
91
70
  console.error(err);
@@ -0,0 +1,2 @@
1
+ export declare const getServerInfo: (quickConnectId: string) => Promise<string>;
2
+ export declare const pingpang: (server: string) => Promise<boolean>;
@@ -0,0 +1,80 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import axios from "axios";
11
+ import { GLOBAL_QUICK_CONNECT_URL, QUICK_CONNECT_PINGPANG_API } from "./constants";
12
+ const getServersFromServerInfo = (serverInfo) => __awaiter(void 0, void 0, void 0, function* () {
13
+ var _a, _b;
14
+ // proxy server
15
+ if (serverInfo.service.relay_ip) {
16
+ const server = `http://${serverInfo.service.relay_ip}:${serverInfo.service.relay_port}`;
17
+ const res = yield pingpang(server);
18
+ if (res) {
19
+ return server;
20
+ }
21
+ }
22
+ // WAN IP
23
+ if (serverInfo.server.external.ip) {
24
+ const server = `http://${serverInfo.server.external.ip}:${serverInfo.service.port}`;
25
+ if (yield pingpang(server)) {
26
+ return server;
27
+ }
28
+ }
29
+ // lan ip
30
+ if ((_a = serverInfo.server.interface) === null || _a === void 0 ? void 0 : _a[0]) {
31
+ const server = `http://${(_b = serverInfo.server.interface) === null || _b === void 0 ? void 0 : _b[0].ip}:${serverInfo.service.port}`;
32
+ if (yield pingpang(server)) {
33
+ return server;
34
+ }
35
+ }
36
+ });
37
+ // get server ip
38
+ export const getServerInfo = (quickConnectId) => __awaiter(void 0, void 0, void 0, function* () {
39
+ var _a, _b, _c, _d;
40
+ const params = {
41
+ version: 1,
42
+ id: "dsm",
43
+ serverID: quickConnectId,
44
+ get_ca_fingerprints: true,
45
+ command: "get_server_info",
46
+ };
47
+ const serverInfo = yield axios.post(GLOBAL_QUICK_CONNECT_URL, params);
48
+ if (!((_b = (_a = serverInfo.data) === null || _a === void 0 ? void 0 : _a.service) === null || _b === void 0 ? void 0 : _b.relay_ip) && !((_d = (_c = serverInfo.data) === null || _c === void 0 ? void 0 : _c.service) === null || _d === void 0 ? void 0 : _d.relay_port)) {
49
+ const relayRequestParams = {
50
+ version: 1,
51
+ id: "dsm",
52
+ serverID: quickConnectId,
53
+ platform: "web",
54
+ command: "request_tunnel",
55
+ };
56
+ const result = yield axios.post(`https://${serverInfo.data.env.control_host}/Serv.php`, relayRequestParams);
57
+ return getServersFromServerInfo(result.data);
58
+ }
59
+ else {
60
+ return getServersFromServerInfo(serverInfo.data);
61
+ }
62
+ });
63
+ // pingpang
64
+ export const pingpang = (server) => __awaiter(void 0, void 0, void 0, function* () {
65
+ try {
66
+ const result = yield axios.get(`${server}/${QUICK_CONNECT_PINGPANG_API}`, {
67
+ timeout: 3000,
68
+ });
69
+ if (result.data.success) {
70
+ return true;
71
+ }
72
+ else {
73
+ return false;
74
+ }
75
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
76
+ }
77
+ catch (_err) {
78
+ return false;
79
+ }
80
+ });
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./core";
2
2
  export * from "./modules";
3
+ export { SynologyApi as default } from "./core";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./core";
2
2
  export * from "./modules";
3
+ export { SynologyApi as default } from "./core";
@@ -0,0 +1,3 @@
1
+ import { SynologyApi } from "../../core";
2
+ export declare function login(core: SynologyApi): Promise<any>;
3
+ export declare function logout(core: SynologyApi): Promise<void>;
@@ -0,0 +1,43 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import axios from "axios";
11
+ import { SYNOLOGY_API_AUTH } from "../../constants";
12
+ export function login(core) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ const params = {
15
+ api: SYNOLOGY_API_AUTH,
16
+ version: 6,
17
+ method: "login",
18
+ account: core.username,
19
+ passwd: core.password,
20
+ format: "sid",
21
+ };
22
+ const url = `${core.baseUrl}entry.cgi`;
23
+ const result = yield axios.get(url, { params });
24
+ if (!result.data.success) {
25
+ throw new Error(result.data.error.message);
26
+ }
27
+ return result.data;
28
+ });
29
+ }
30
+ export function logout(core) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ const params = {
33
+ api: SYNOLOGY_API_AUTH,
34
+ version: 6,
35
+ method: "logout",
36
+ };
37
+ const url = `${core.baseUrl}entry.cgi`;
38
+ const result = yield axios.get(url, { params });
39
+ if (!result.data.success) {
40
+ throw new Error(result.data.error.message);
41
+ }
42
+ });
43
+ }
@@ -0,0 +1,2 @@
1
+ import { SynologyApi } from "../../core";
2
+ export declare function getApiInfo(core: SynologyApi): Promise<any>;
@@ -0,0 +1,26 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import axios from "axios";
11
+ import { SYNOLOGY_API_INFO } from "../../constants";
12
+ export function getApiInfo(core) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ const params = {
15
+ api: SYNOLOGY_API_INFO,
16
+ version: 1,
17
+ method: "query",
18
+ };
19
+ const url = `${core.baseUrl}entry.cgi`;
20
+ const result = yield axios.get(url, { params });
21
+ if (!result.data.success) {
22
+ throw new Error(result.data.error.message);
23
+ }
24
+ return result.data;
25
+ });
26
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./Auth";
2
+ export * from "./Info";
@@ -0,0 +1,2 @@
1
+ export * from "./Auth";
2
+ export * from "./Info";
@@ -0,0 +1,5 @@
1
+ import { AudioStationSongListRequest, AudioStationSongListResponse } from "./types";
2
+ /**
3
+ * fetch song list
4
+ * */
5
+ export declare function getSongList(params: AudioStationSongListRequest): Promise<AudioStationSongListResponse>;
@@ -7,7 +7,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- function getSongList(params) {
10
+ /**
11
+ * fetch song list
12
+ * */
13
+ export function getSongList(params) {
11
14
  return __awaiter(this, void 0, void 0, function* () {
12
15
  const res = yield this.run("SYNO.AudioStation.Song", {
13
16
  params: {
@@ -20,7 +23,3 @@ function getSongList(params) {
20
23
  return res.data;
21
24
  });
22
25
  }
23
- export const AudioStationMethods = {
24
- getSongList,
25
- };
26
- export const AudioStationProp = "AudioStation";
@@ -1,17 +1,18 @@
1
+ import { SynologyApiResponse } from "../../../types";
1
2
  export type AudioStationSongListRequest = {
2
3
  limit?: number;
3
4
  offset?: number;
4
- method?: number;
5
- library?: number;
5
+ method?: string;
6
+ library?: string;
6
7
  additional?: string;
7
8
  version?: number;
8
9
  sort_by?: string;
9
10
  sort_direction?: string;
10
11
  };
11
- export type AudioStationSongListResponse = {
12
+ export type AudioStationSongListResponse = SynologyApiResponse<{
12
13
  offset: number;
13
14
  total: number;
14
15
  songs: {
15
16
  [key: string]: any;
16
17
  };
17
- };
18
+ }>;
@@ -0,0 +1,5 @@
1
+ import { getSongList } from "./Song";
2
+ export declare const AudioStationMethods: {
3
+ getSongList: typeof getSongList;
4
+ };
5
+ export declare const AudioStationKey = "AudioStation";
@@ -0,0 +1,5 @@
1
+ import { getSongList } from "./Song";
2
+ export const AudioStationMethods = {
3
+ getSongList: getSongList,
4
+ };
5
+ export const AudioStationKey = "AudioStation";
@@ -1,5 +1,5 @@
1
- import { AudioStationProp, AudioStationMethods } from "./AudioStation";
1
+ import { AudioStationKey, AudioStationMethods } from "./AudioStation";
2
2
  export declare class BaseSynologyApi {
3
- [AudioStationProp]: typeof AudioStationMethods;
3
+ [AudioStationKey]: typeof AudioStationMethods;
4
4
  constructor();
5
5
  }
@@ -1,15 +1,20 @@
1
- import { AudioStationProp, AudioStationMethods } from "./AudioStation";
1
+ import { AudioStationKey, AudioStationMethods } from "./AudioStation";
2
+ // bind methods to BaseSynologyApi instance
3
+ function methodsBundler(instance, methods) {
4
+ const output = {};
5
+ for (const key in methods) {
6
+ output[key] = methods[key].bind(instance);
7
+ }
8
+ return output;
9
+ }
2
10
  export class BaseSynologyApi {
3
11
  constructor() { }
4
12
  }
5
- const instanceBindings = new WeakMap();
6
- Object.defineProperty(BaseSynologyApi.prototype, AudioStationProp, {
7
- get() {
8
- if (!instanceBindings.has(this)) {
9
- instanceBindings.set(this, {
10
- getSongList: AudioStationMethods.getSongList.bind(this),
11
- });
12
- }
13
- return instanceBindings.get(this);
13
+ // proxy methods namespace to BaseSynologyApi instance
14
+ Object.defineProperties(BaseSynologyApi.prototype, {
15
+ [AudioStationKey]: {
16
+ get() {
17
+ return methodsBundler(this, AudioStationMethods);
18
+ },
14
19
  },
15
20
  });
@@ -0,0 +1,9 @@
1
+ export type SynologyApiResponse<T = any> = {
2
+ data: T;
3
+ error: {
4
+ code: number;
5
+ errors: Record<string, string>;
6
+ message: string;
7
+ };
8
+ success: boolean;
9
+ };
package/dist/types/API.js CHANGED
@@ -1 +1,2 @@
1
- // export type
1
+ // export response
2
+ export {};
@@ -0,0 +1 @@
1
+ export * from "./API";
@@ -0,0 +1 @@
1
+ export * from "./API";
package/dist/utils.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export declare function isEmpty(obj: Record<string, any>): boolean;
1
+ export declare function isObjEmpty(obj: Record<string, any>): boolean;
2
2
  export declare function queryObjToString(params: Record<string, any>): string;
3
+ export declare function isHttpUrl(url: string): boolean;
package/dist/utils.js CHANGED
@@ -1,11 +1,14 @@
1
- export function isEmpty(obj) {
2
- return Object.keys(obj).length === 0;
1
+ export function isObjEmpty(obj) {
2
+ return obj && typeof obj === "object" && Object.keys(obj).length === 0;
3
3
  }
4
4
  export function queryObjToString(params) {
5
- if (isEmpty(params)) {
5
+ if (isObjEmpty(params)) {
6
6
  return "";
7
7
  }
8
8
  return Object.keys(params)
9
9
  .map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(params[key]))
10
10
  .join("&");
11
11
  }
12
+ export function isHttpUrl(url) {
13
+ return /^https?:\/\//.test(url);
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fett/synology-api",
3
- "version": "0.0.1-beta.0",
3
+ "version": "0.0.1-beta.1",
4
4
  "description": "synology api for nodejs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -10,6 +10,7 @@
10
10
  "synology",
11
11
  "api",
12
12
  "nodejs",
13
+ "browser",
13
14
  "typescript",
14
15
  "esmodule"
15
16
  ],
@@ -17,8 +18,8 @@
17
18
  "dist"
18
19
  ],
19
20
  "scripts": {
20
- "dev": "tsc --sourceMap --watch",
21
- "build": "npm run clean && tsc",
21
+ "dev": "tsc && tsc-alias --sourceMap --watch",
22
+ "build": "npm run clean && tsc && tsc-alias",
22
23
  "lint": "eslint src --ext .ts --fix",
23
24
  "prettier": "prettier --write 'src/**/*.ts'",
24
25
  "format": "npm run prettier && npm run lint",
@@ -37,6 +38,7 @@
37
38
  "prettier": "3.5.3",
38
39
  "rimraf": "^6.0.1",
39
40
  "ts-node": "^10.9.2",
41
+ "tsc-alias": "^1.8.16",
40
42
  "typescript": "~5.8.3",
41
43
  "typescript-eslint": "^8.32.0",
42
44
  "vite": "^6.3.5",
@@ -1,7 +0,0 @@
1
- import { AudioStationSongListRequest, AudioStationSongListResponse } from "../types/AudioStation.Song";
2
- declare function getSongList(params: AudioStationSongListRequest): Promise<AudioStationSongListResponse>;
3
- export declare const AudioStationMethods: {
4
- getSongList: typeof getSongList;
5
- };
6
- export declare const AudioStationProp = "AudioStation";
7
- export {};
File without changes
File without changes