@wdywdy/electric-open-platform 0.0.0-stage → 1.0.0

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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +585 -2
  3. package/dist/cjs/client.js +173 -0
  4. package/dist/cjs/client.js.map +1 -0
  5. package/dist/cjs/core/api.js +518 -0
  6. package/dist/cjs/core/api.js.map +1 -0
  7. package/dist/cjs/core/auth.js +293 -0
  8. package/dist/cjs/core/auth.js.map +1 -0
  9. package/dist/cjs/core/crypto.js +160 -0
  10. package/dist/cjs/core/crypto.js.map +1 -0
  11. package/dist/cjs/core/errors.js +114 -0
  12. package/dist/cjs/core/errors.js.map +1 -0
  13. package/dist/cjs/core/http.js +70 -0
  14. package/dist/cjs/core/http.js.map +1 -0
  15. package/dist/cjs/core/models.js +105 -0
  16. package/dist/cjs/core/models.js.map +1 -0
  17. package/dist/cjs/core/push.js +79 -0
  18. package/dist/cjs/core/push.js.map +1 -0
  19. package/dist/cjs/core/router.js +124 -0
  20. package/dist/cjs/core/router.js.map +1 -0
  21. package/dist/cjs/core/runtime.js +37 -0
  22. package/dist/cjs/core/runtime.js.map +1 -0
  23. package/dist/cjs/core/sign.js +75 -0
  24. package/dist/cjs/core/sign.js.map +1 -0
  25. package/dist/cjs/core/storage.js +152 -0
  26. package/dist/cjs/core/storage.js.map +1 -0
  27. package/dist/cjs/core/transport-fetch.js +57 -0
  28. package/dist/cjs/core/transport-fetch.js.map +1 -0
  29. package/dist/cjs/core/transport.js +41 -0
  30. package/dist/cjs/core/transport.js.map +1 -0
  31. package/dist/cjs/core/types.js +5 -0
  32. package/dist/cjs/core/types.js.map +1 -0
  33. package/dist/cjs/index.js +111 -0
  34. package/dist/cjs/index.js.map +1 -0
  35. package/dist/cjs/package.json +3 -0
  36. package/dist/esm/client.d.ts +73 -0
  37. package/dist/esm/client.js +169 -0
  38. package/dist/esm/client.js.map +1 -0
  39. package/dist/esm/core/api.d.ts +377 -0
  40. package/dist/esm/core/api.js +506 -0
  41. package/dist/esm/core/api.js.map +1 -0
  42. package/dist/esm/core/auth.d.ts +69 -0
  43. package/dist/esm/core/auth.js +289 -0
  44. package/dist/esm/core/auth.js.map +1 -0
  45. package/dist/esm/core/crypto.d.ts +24 -0
  46. package/dist/esm/core/crypto.js +154 -0
  47. package/dist/esm/core/crypto.js.map +1 -0
  48. package/dist/esm/core/errors.d.ts +86 -0
  49. package/dist/esm/core/errors.js +108 -0
  50. package/dist/esm/core/errors.js.map +1 -0
  51. package/dist/esm/core/http.d.ts +24 -0
  52. package/dist/esm/core/http.js +56 -0
  53. package/dist/esm/core/http.js.map +1 -0
  54. package/dist/esm/core/models.d.ts +441 -0
  55. package/dist/esm/core/models.js +102 -0
  56. package/dist/esm/core/models.js.map +1 -0
  57. package/dist/esm/core/push.d.ts +54 -0
  58. package/dist/esm/core/push.js +74 -0
  59. package/dist/esm/core/push.js.map +1 -0
  60. package/dist/esm/core/router.d.ts +44 -0
  61. package/dist/esm/core/router.js +120 -0
  62. package/dist/esm/core/router.js.map +1 -0
  63. package/dist/esm/core/runtime.d.ts +23 -0
  64. package/dist/esm/core/runtime.js +33 -0
  65. package/dist/esm/core/runtime.js.map +1 -0
  66. package/dist/esm/core/sign.d.ts +54 -0
  67. package/dist/esm/core/sign.js +67 -0
  68. package/dist/esm/core/sign.js.map +1 -0
  69. package/dist/esm/core/storage.d.ts +73 -0
  70. package/dist/esm/core/storage.js +141 -0
  71. package/dist/esm/core/storage.js.map +1 -0
  72. package/dist/esm/core/transport-fetch.d.ts +2 -0
  73. package/dist/esm/core/transport-fetch.js +54 -0
  74. package/dist/esm/core/transport-fetch.js.map +1 -0
  75. package/dist/esm/core/transport.d.ts +23 -0
  76. package/dist/esm/core/transport.js +36 -0
  77. package/dist/esm/core/transport.js.map +1 -0
  78. package/dist/esm/core/types.d.ts +76 -0
  79. package/dist/esm/core/types.js +2 -0
  80. package/dist/esm/core/types.js.map +1 -0
  81. package/dist/esm/index.d.ts +37 -0
  82. package/dist/esm/index.js +47 -0
  83. package/dist/esm/index.js.map +1 -0
  84. package/package.json +63 -4
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Typed models for the open platform's `data` payloads.
3
+ *
4
+ * Field names mirror the wire format exactly (including the odd ones such as
5
+ * `aTUp` or `mxgl`) so that mapping mistakes are caught at compile time rather
6
+ * than surfacing as `undefined` in the UI.
7
+ */
8
+ /** 设备类型 */
9
+ export var EquipmentType;
10
+ (function (EquipmentType) {
11
+ EquipmentType[EquipmentType["MicroBreaker"] = 1] = "MicroBreaker";
12
+ EquipmentType[EquipmentType["ElectricalFire"] = 2] = "ElectricalFire";
13
+ EquipmentType[EquipmentType["LiquidLevel"] = 4] = "LiquidLevel";
14
+ EquipmentType[EquipmentType["Pressure"] = 5] = "Pressure";
15
+ EquipmentType[EquipmentType["SmokeForSafe"] = 6] = "SmokeForSafe";
16
+ EquipmentType[EquipmentType["GasForSafe"] = 7] = "GasForSafe";
17
+ EquipmentType[EquipmentType["SmokeZac"] = 10] = "SmokeZac";
18
+ EquipmentType[EquipmentType["TempHumidity"] = 11] = "TempHumidity";
19
+ EquipmentType[EquipmentType["SmartMeter"] = 12] = "SmartMeter";
20
+ EquipmentType[EquipmentType["GasZac"] = 13] = "GasZac";
21
+ EquipmentType[EquipmentType["Lux"] = 14] = "Lux";
22
+ EquipmentType[EquipmentType["SmartWaterMeter"] = 16] = "SmartWaterMeter";
23
+ EquipmentType[EquipmentType["SmokeWanlin"] = 29] = "SmokeWanlin";
24
+ EquipmentType[EquipmentType["TempWanlin"] = 39] = "TempWanlin";
25
+ EquipmentType[EquipmentType["SmokeHaiman"] = 42] = "SmokeHaiman";
26
+ EquipmentType[EquipmentType["GasHaiman"] = 45] = "GasHaiman";
27
+ })(EquipmentType || (EquipmentType = {}));
28
+ export const AlarmType = {
29
+ ShortCircuit: 1,
30
+ LeakageMonitor: 2,
31
+ Overload: 3,
32
+ Overcurrent: 4,
33
+ Overvoltage: 5,
34
+ Undervoltage: 6,
35
+ Temperature: 7,
36
+ Surge: 8,
37
+ LeakageProtectOk: 9,
38
+ LeakageSelfTestIncomplete: 10,
39
+ Arc: 11,
40
+ LeakageWarning: 12,
41
+ CurrentWarning: 13,
42
+ OvervoltageWarning: 14,
43
+ UndervoltageWarning: 15,
44
+ CommWarning: 16,
45
+ PhaseLoss: 17,
46
+ ThreePhaseUnbalance: 18,
47
+ ThreePhaseSequence: 19,
48
+ PowerLoss: 53,
49
+ BoxOpen: 54,
50
+ TemperatureWarning: 55,
51
+ Humidity: 56,
52
+ Input: 57,
53
+ PowerNormal: 58,
54
+ MalignantLoad: 62,
55
+ Config: 1001,
56
+ Offline: 1002,
57
+ SwitchOffNotice: 1003,
58
+ SwitchOnNotice: 1004,
59
+ AbnormalOpen: 1005,
60
+ LeakageProtection: 1016,
61
+ Smoke: 2002,
62
+ Gas: 2006,
63
+ Pressure: 2003,
64
+ LiquidLevel: 2004,
65
+ DeviceFault: 9000,
66
+ CommFault: 9001,
67
+ };
68
+ /** Push type 1=告警 2=节点状态 3=事件通知 */
69
+ export var PushType;
70
+ (function (PushType) {
71
+ PushType[PushType["Alarm"] = 1] = "Alarm";
72
+ PushType[PushType["ChannelStatus"] = 2] = "ChannelStatus";
73
+ PushType[PushType["Event"] = 3] = "Event";
74
+ })(PushType || (PushType = {}));
75
+ /**
76
+ * 第三方设备类型(GET_THR_DEVICE_STATUS 的 equipmentType)。
77
+ * 注意:这些是「非微断」设备,微断(1)/电气火灾(2) 不走这个接口。
78
+ */
79
+ export var ThirdPartyEquipmentType;
80
+ (function (ThirdPartyEquipmentType) {
81
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["LiquidLevel"] = 4] = "LiquidLevel";
82
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["Pressure"] = 5] = "Pressure";
83
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["SmokeForSafe"] = 6] = "SmokeForSafe";
84
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["GasForSafe"] = 7] = "GasForSafe";
85
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["SmokeZac"] = 10] = "SmokeZac";
86
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["TempHumidity"] = 11] = "TempHumidity";
87
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["SmartMeter"] = 12] = "SmartMeter";
88
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["GasZac"] = 13] = "GasZac";
89
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["Lux"] = 14] = "Lux";
90
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["SmartWaterMeter"] = 16] = "SmartWaterMeter";
91
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["SmokeWanlin"] = 29] = "SmokeWanlin";
92
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["TempWanlin"] = 39] = "TempWanlin";
93
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["SmokeHaiman"] = 42] = "SmokeHaiman";
94
+ ThirdPartyEquipmentType[ThirdPartyEquipmentType["GasHaiman"] = 45] = "GasHaiman";
95
+ })(ThirdPartyEquipmentType || (ThirdPartyEquipmentType = {}));
96
+ /** 智能水表(CHaobiamao) 查询模式:0=实时抄表,1=时间范围 */
97
+ export var MeterQueryType;
98
+ (function (MeterQueryType) {
99
+ MeterQueryType[MeterQueryType["Realtime"] = 0] = "Realtime";
100
+ MeterQueryType[MeterQueryType["Range"] = 1] = "Range";
101
+ })(MeterQueryType || (MeterQueryType = {}));
102
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../../src/core/models.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,WAAW;AACX,MAAM,CAAN,IAAY,aAiBX;AAjBD,WAAY,aAAa;IACvB,iEAAgB,CAAA;IAChB,qEAAkB,CAAA;IAClB,+DAAe,CAAA;IACf,yDAAY,CAAA;IACZ,iEAAgB,CAAA;IAChB,6DAAc,CAAA;IACd,0DAAa,CAAA;IACb,kEAAiB,CAAA;IACjB,8DAAe,CAAA;IACf,sDAAW,CAAA;IACX,gDAAQ,CAAA;IACR,wEAAoB,CAAA;IACpB,gEAAgB,CAAA;IAChB,8DAAe,CAAA;IACf,gEAAgB,CAAA;IAChB,4DAAc,CAAA;AAChB,CAAC,EAjBW,aAAa,KAAb,aAAa,QAiBxB;AAmLD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,YAAY,EAAE,CAAC;IACf,cAAc,EAAE,CAAC;IACjB,QAAQ,EAAE,CAAC;IACX,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,CAAC;IACd,YAAY,EAAE,CAAC;IACf,WAAW,EAAE,CAAC;IACd,KAAK,EAAE,CAAC;IACR,gBAAgB,EAAE,CAAC;IACnB,yBAAyB,EAAE,EAAE;IAC7B,GAAG,EAAE,EAAE;IACP,cAAc,EAAE,EAAE;IAClB,cAAc,EAAE,EAAE;IAClB,kBAAkB,EAAE,EAAE;IACtB,mBAAmB,EAAE,EAAE;IACvB,WAAW,EAAE,EAAE;IACf,SAAS,EAAE,EAAE;IACb,mBAAmB,EAAE,EAAE;IACvB,kBAAkB,EAAE,EAAE;IACtB,SAAS,EAAE,EAAE;IACb,OAAO,EAAE,EAAE;IACX,kBAAkB,EAAE,EAAE;IACtB,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE;IACT,WAAW,EAAE,EAAE;IACf,aAAa,EAAE,EAAE;IACjB,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,IAAI;IACpB,YAAY,EAAE,IAAI;IAClB,iBAAiB,EAAE,IAAI;IACvB,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,IAAI;CACP,CAAC;AA8GX,mCAAmC;AACnC,MAAM,CAAN,IAAY,QAIX;AAJD,WAAY,QAAQ;IAClB,yCAAS,CAAA;IACT,yDAAiB,CAAA;IACjB,yCAAS,CAAA;AACX,CAAC,EAJW,QAAQ,KAAR,QAAQ,QAInB;AAED;;;GAGG;AACH,MAAM,CAAN,IAAY,uBAeX;AAfD,WAAY,uBAAuB;IACjC,mFAAe,CAAA;IACf,6EAAY,CAAA;IACZ,qFAAgB,CAAA;IAChB,iFAAc,CAAA;IACd,8EAAa,CAAA;IACb,sFAAiB,CAAA;IACjB,kFAAe,CAAA;IACf,0EAAW,CAAA;IACX,oEAAQ,CAAA;IACR,4FAAoB,CAAA;IACpB,oFAAgB,CAAA;IAChB,kFAAe,CAAA;IACf,oFAAgB,CAAA;IAChB,gFAAc,CAAA;AAChB,CAAC,EAfW,uBAAuB,KAAvB,uBAAuB,QAelC;AAmBD,0CAA0C;AAC1C,MAAM,CAAN,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,2DAAY,CAAA;IACZ,qDAAS,CAAA;AACX,CAAC,EAHW,cAAc,KAAd,cAAc,QAGzB"}
@@ -0,0 +1,54 @@
1
+ import { RouterClient } from './router.js';
2
+ import { PushSubscription, PushType } from './models.js';
3
+ export type PushCmd = 'QUERY' | 'SUBSCRIBE' | 'UNSUBSCRIBE' | 'FAILED_CHECK' | 'FAILED_RESET';
4
+ /**
5
+ * 消息推送订阅管理 SUBSCRIBE_PUSH_SERVICE.
6
+ *
7
+ * Note this is *server → your backend* push, not a WebSocket to the app. On a
8
+ * mobile app you normally keep using polling (or run the receiver on your own
9
+ * backend and relay via your own channel); the helpers here exist so an app can
10
+ * configure/inspect the subscription from a settings screen.
11
+ */
12
+ export declare class PushApi {
13
+ private readonly client;
14
+ constructor(client: RouterClient);
15
+ private call;
16
+ /** QUERY — inspect the current subscription for a push type. */
17
+ query(pushType: PushType): Promise<PushSubscription | null>;
18
+ /**
19
+ * SUBSCRIBE — also used to modify (same pushType replaces the previous one).
20
+ * IMPORTANT: the platform immediately POSTs a `detect` probe to `url` and
21
+ * fails the call ("url请求错误") unless your endpoint answers `{code:0, msg:<detect>}`.
22
+ */
23
+ subscribe(args: {
24
+ pushType: PushType;
25
+ url: string;
26
+ salt: string;
27
+ timeRanges: string;
28
+ projectCode?: string;
29
+ pushEnable: 0 | 1;
30
+ /** Required for pushType=1; pass '0' for status/event pushes. */
31
+ typeNumbers?: string | number[];
32
+ pushMode?: '1';
33
+ equipmentTypes?: string | number[];
34
+ }): Promise<unknown>;
35
+ unsubscribe(projectCode?: string): Promise<unknown>;
36
+ /** FAILED_CHECK — is the endpoint still healthy? */
37
+ failedCheck(pushType: PushType): Promise<PushSubscription>;
38
+ /** FAILED_RESET — re-enable pushing after the platform auto-disabled it. */
39
+ failedReset(pushType: PushType, pushEnable?: 0 | 1): Promise<unknown>;
40
+ }
41
+ /**
42
+ * Verifies an inbound push against the documented `key` scheme, for use in the
43
+ * backend endpoint you register via {@link PushApi.subscribe}.
44
+ *
45
+ * `key = md5(salt + dateTime)` where dateTime is the alarm date (`yyyy-MM-dd`);
46
+ * in batch mode (`pushMode: "1"`) the platform uses today's date instead.
47
+ */
48
+ export declare function verifyPushKey(args: {
49
+ salt: string;
50
+ key: string;
51
+ dateTime?: string;
52
+ batch?: boolean;
53
+ now?: Date;
54
+ }): boolean;
@@ -0,0 +1,74 @@
1
+ import { md5 as md5Hex } from './crypto.js';
2
+ import { PushType } from './models.js';
3
+ /**
4
+ * 消息推送订阅管理 SUBSCRIBE_PUSH_SERVICE.
5
+ *
6
+ * Note this is *server → your backend* push, not a WebSocket to the app. On a
7
+ * mobile app you normally keep using polling (or run the receiver on your own
8
+ * backend and relay via your own channel); the helpers here exist so an app can
9
+ * configure/inspect the subscription from a settings screen.
10
+ */
11
+ export class PushApi {
12
+ constructor(client) {
13
+ this.client = client;
14
+ }
15
+ call(cmd, params = {}) {
16
+ return this.client.call('SUBSCRIBE_PUSH_SERVICE', { params: { cmd, ...params } });
17
+ }
18
+ /** QUERY — inspect the current subscription for a push type. */
19
+ query(pushType) {
20
+ return this.call('QUERY', { pushType });
21
+ }
22
+ /**
23
+ * SUBSCRIBE — also used to modify (same pushType replaces the previous one).
24
+ * IMPORTANT: the platform immediately POSTs a `detect` probe to `url` and
25
+ * fails the call ("url请求错误") unless your endpoint answers `{code:0, msg:<detect>}`.
26
+ */
27
+ subscribe(args) {
28
+ const typeNumbers = Array.isArray(args.typeNumbers)
29
+ ? args.typeNumbers.join(',')
30
+ : (args.typeNumbers ?? (args.pushType === PushType.Alarm ? '-1' : '0'));
31
+ return this.call('SUBSCRIBE', {
32
+ pushType: args.pushType,
33
+ url: args.url,
34
+ salt: args.salt,
35
+ timeRanges: args.timeRanges,
36
+ projectCode: args.projectCode ?? '*',
37
+ pushEnable: args.pushEnable,
38
+ typeNumbers,
39
+ pushMode: args.pushMode,
40
+ equipmentTypes: Array.isArray(args.equipmentTypes)
41
+ ? args.equipmentTypes.join(',')
42
+ : args.equipmentTypes,
43
+ });
44
+ }
45
+ unsubscribe(projectCode = '*') {
46
+ return this.call('UNSUBSCRIBE', { projectCode });
47
+ }
48
+ /** FAILED_CHECK — is the endpoint still healthy? */
49
+ failedCheck(pushType) {
50
+ return this.call('FAILED_CHECK', { pushType });
51
+ }
52
+ /** FAILED_RESET — re-enable pushing after the platform auto-disabled it. */
53
+ failedReset(pushType, pushEnable = 1) {
54
+ return this.call('FAILED_RESET', { pushType, pushEnable });
55
+ }
56
+ }
57
+ /**
58
+ * Verifies an inbound push against the documented `key` scheme, for use in the
59
+ * backend endpoint you register via {@link PushApi.subscribe}.
60
+ *
61
+ * `key = md5(salt + dateTime)` where dateTime is the alarm date (`yyyy-MM-dd`);
62
+ * in batch mode (`pushMode: "1"`) the platform uses today's date instead.
63
+ */
64
+ export function verifyPushKey(args) {
65
+ const day = args.batch ? formatDay(args.now ?? new Date()) : (args.dateTime ?? '').slice(0, 10);
66
+ if (!day)
67
+ return false;
68
+ return md5Hex(args.salt + day) === args.key.toLowerCase();
69
+ }
70
+ function formatDay(d) {
71
+ const pad = (n) => String(n).padStart(2, '0');
72
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
73
+ }
74
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../../src/core/push.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `POST /invoke/router.as` client.
3
+ *
4
+ * Responsibilities:
5
+ * - sign every request (MD5 over sorted values + appSecret);
6
+ * - inject a valid access token, transparently refreshing it once per call when
7
+ * the platform answers `code: "2"` (token expired);
8
+ * - unwrap the `{ code, message, data }` envelope into plain data or throw an
9
+ * {@link ElectricOpenPlatformError} carrying the platform code.
10
+ */
11
+ import { AuthManager } from './auth.js';
12
+ import { ParamValue } from './sign.js';
13
+ import { ElectricConfig } from './types.js';
14
+ export interface CallOptions {
15
+ /** Overrides the configured default project code. */
16
+ projectCode?: string;
17
+ params?: Record<string, ParamValue>;
18
+ timeoutMs?: number;
19
+ signal?: AbortSignal;
20
+ /** Internal: prevents an endless refresh → retry → refresh loop. */
21
+ _retried?: boolean;
22
+ }
23
+ export declare class RouterClient {
24
+ private readonly config;
25
+ private readonly auth;
26
+ private readonly logger?;
27
+ private readonly credentials;
28
+ /** Once any signed call succeeds we can attribute 签名 errors to provisioning. */
29
+ private hasAnySuccess;
30
+ constructor(config: ElectricConfig, auth: AuthManager, credentials: () => {
31
+ username?: string;
32
+ password?: string;
33
+ });
34
+ /**
35
+ * Calls one platform method and returns its `data` payload.
36
+ *
37
+ * @throws ElectricOpenPlatformError for transport failures and non-zero codes.
38
+ */
39
+ call<T>(method: string, options?: CallOptions): Promise<T>;
40
+ /** Convenience: call a method that returns a paginated `data` object. */
41
+ callPaged<T>(method: string, options?: CallOptions): Promise<T>;
42
+ private renewToken;
43
+ private emit;
44
+ }
@@ -0,0 +1,120 @@
1
+ import { ElectricOpenPlatformError, PlatformCodes, TransportCodes } from './errors.js';
2
+ import { postForm } from './http.js';
3
+ import { buildSignedRequest } from './sign.js';
4
+ import { DEFAULT_BASE_URL } from './types.js';
5
+ const ROUTER_PATH = '/invoke/router.as';
6
+ /** Params controlled by the client — rejected in the caller's `params`. */
7
+ const RESERVED_PARAMS = new Set(['client_id', 'method', 'access_token', 'timestamp', 'sign']);
8
+ export class RouterClient {
9
+ constructor(config, auth, credentials) {
10
+ /** Once any signed call succeeds we can attribute 签名 errors to provisioning. */
11
+ this.hasAnySuccess = false;
12
+ this.auth = auth;
13
+ this.logger = config.logger;
14
+ this.credentials = credentials;
15
+ this.config = {
16
+ appKey: config.appKey,
17
+ appSecret: config.appSecret,
18
+ baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ''),
19
+ timeoutMs: config.timeoutMs ?? 20000,
20
+ projectCode: config.projectCode,
21
+ };
22
+ }
23
+ /**
24
+ * Calls one platform method and returns its `data` payload.
25
+ *
26
+ * @throws ElectricOpenPlatformError for transport failures and non-zero codes.
27
+ */
28
+ async call(method, options = {}) {
29
+ const params = { ...(options.params ?? {}) };
30
+ for (const key of Object.keys(params)) {
31
+ if (RESERVED_PARAMS.has(key)) {
32
+ throw new ElectricOpenPlatformError(TransportCodes.INVALID_ARGUMENT, `参数 ${key} 由插件自动生成,请勿手动传入`, { method });
33
+ }
34
+ }
35
+ const projectCode = options.projectCode ?? this.config.projectCode;
36
+ if (projectCode !== undefined && params.projectCode === undefined) {
37
+ params.projectCode = projectCode;
38
+ }
39
+ const accessToken = await this.auth.getAccessToken(this.credentials());
40
+ const { body, params: signed } = buildSignedRequest({
41
+ appKey: this.config.appKey,
42
+ appSecret: this.config.appSecret,
43
+ method,
44
+ accessToken,
45
+ params,
46
+ });
47
+ this.emit('request', method, {
48
+ timestamp: signed.timestamp,
49
+ params: redact(signed),
50
+ });
51
+ const res = await postForm({
52
+ url: `${this.config.baseUrl}${ROUTER_PATH}`,
53
+ body,
54
+ timeoutMs: options.timeoutMs ?? this.config.timeoutMs,
55
+ signal: options.signal,
56
+ });
57
+ const envelope = res.data;
58
+ if (!envelope || typeof envelope !== 'object') {
59
+ throw new ElectricOpenPlatformError(TransportCodes.PARSE, '接口返回格式异常(非 JSON 对象)', { method, details: res.raw });
60
+ }
61
+ const code = String(envelope.code ?? PlatformCodes.FAILED);
62
+ if (code === PlatformCodes.OK) {
63
+ this.hasAnySuccess = true;
64
+ this.emit('response', method, { code, success: envelope.success });
65
+ return envelope.data;
66
+ }
67
+ // Token expired / session gone: renew once, then retry the same request.
68
+ if ((code === PlatformCodes.TOKEN_EXPIRED || code === PlatformCodes.NOT_LOGGED_IN) && !options._retried) {
69
+ this.emit('error', method, { code, message: 'accessToken 失效,正在刷新后重试' });
70
+ await this.renewToken(code);
71
+ return this.call(method, { ...options, _retried: true });
72
+ }
73
+ const err = new ElectricOpenPlatformError(code, envelope.message, {
74
+ method,
75
+ details: res.data,
76
+ });
77
+ this.emit('error', method, { code, message: envelope.message });
78
+ if (envelope.message && /限额|超过.*次数|quota/i.test(envelope.message)) {
79
+ throw new ElectricOpenPlatformError(TransportCodes.QUOTA, envelope.message, {
80
+ method,
81
+ details: res.data,
82
+ });
83
+ }
84
+ // The platform signals "method not enabled for this appKey" with either
85
+ // "接口名称(X)无效!" or — misleadingly — "无效的接口方法参数签名!".
86
+ // We can only be sure it is a provisioning problem (and not a signing bug)
87
+ // when another call with the same key has already succeeded this session.
88
+ const notProvisionedHint = /接口方法参数签名/.test(envelope.message ?? '') ||
89
+ /接口名称\(.*\)无效/.test(envelope.message ?? '');
90
+ if (notProvisionedHint && this.hasAnySuccess) {
91
+ throw new ElectricOpenPlatformError(TransportCodes.NOT_PROVISIONED, `接口 ${method} 未对当前 appKey 开通(平台提示:${envelope.message})。请联系平台商务开通该接口权限。`, { method, details: res.data });
92
+ }
93
+ throw err;
94
+ }
95
+ /** Convenience: call a method that returns a paginated `data` object. */
96
+ async callPaged(method, options = {}) {
97
+ return this.call(method, options);
98
+ }
99
+ async renewToken(code) {
100
+ this.emit('token', 'router', { reason: `code=${code}` });
101
+ await this.auth.recover(this.credentials());
102
+ }
103
+ emit(type, method, detail) {
104
+ this.logger?.({ type, method, detail, message: `${type} ${method}`, at: new Date().toISOString() });
105
+ }
106
+ }
107
+ /** Strips secrets from a logged params object. */
108
+ function redact(params) {
109
+ const copy = { ...params };
110
+ if (copy.sign)
111
+ copy.sign = `${copy.sign.slice(0, 6)}…`;
112
+ if (copy.access_token)
113
+ copy.access_token = `${copy.access_token.slice(0, 6)}…`;
114
+ if (copy.passwd)
115
+ copy.passwd = '***';
116
+ if (copy.client_secret)
117
+ copy.client_secret = `${copy.client_secret.slice(0, 6)}…`;
118
+ return copy;
119
+ }
120
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../../src/core/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 运行时探测 —— **只用于诊断输出,不参与任何能力选择**。
3
+ *
4
+ * 旧版本会按宿主环境自动装配三套传输实现(原生 HTTP / 原生桥 / fetch),
5
+ * 那是本包与 Capacitor、Cordova 耦合的唯一来源。现在传输实现只来自:
6
+ *
7
+ * 1. `configure({ transport })` 显式指定
8
+ * 2. `configure()` 之前用 `setTransport()` 注入
9
+ * 3. 默认 {@link createFetchTransport}
10
+ *
11
+ * 也就是说:**同一个包在 Node、浏览器、Electron、小程序容器里行为完全一致**,
12
+ * 差异只由使用方注入的 transport / store 决定,而不是由运行时的全局对象猜出来。
13
+ */
14
+ export type Runtime = 'node' | 'browser' | 'unknown';
15
+ /**
16
+ * 判断当前运行时,不抛错。
17
+ *
18
+ * Electron 渲染进程同时有 `window` 与 `process.versions.node`,这里归为 `browser`
19
+ * (它的网络能力仍然受 WebView 同源策略限制);主进程归为 `node`。
20
+ */
21
+ export declare function detectRuntime(): Runtime;
22
+ /** 当前运行时是否有可用的全局 `fetch`(Node ≥ 18 / 现代浏览器都有)。 */
23
+ export declare function hasGlobalFetch(): boolean;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 运行时探测 —— **只用于诊断输出,不参与任何能力选择**。
3
+ *
4
+ * 旧版本会按宿主环境自动装配三套传输实现(原生 HTTP / 原生桥 / fetch),
5
+ * 那是本包与 Capacitor、Cordova 耦合的唯一来源。现在传输实现只来自:
6
+ *
7
+ * 1. `configure({ transport })` 显式指定
8
+ * 2. `configure()` 之前用 `setTransport()` 注入
9
+ * 3. 默认 {@link createFetchTransport}
10
+ *
11
+ * 也就是说:**同一个包在 Node、浏览器、Electron、小程序容器里行为完全一致**,
12
+ * 差异只由使用方注入的 transport / store 决定,而不是由运行时的全局对象猜出来。
13
+ */
14
+ /**
15
+ * 判断当前运行时,不抛错。
16
+ *
17
+ * Electron 渲染进程同时有 `window` 与 `process.versions.node`,这里归为 `browser`
18
+ * (它的网络能力仍然受 WebView 同源策略限制);主进程归为 `node`。
19
+ */
20
+ export function detectRuntime() {
21
+ const g = globalThis;
22
+ const hasWindow = typeof g.window !== 'undefined' && typeof g.document !== 'undefined';
23
+ if (hasWindow)
24
+ return 'browser';
25
+ if (typeof g.process?.versions?.node === 'string')
26
+ return 'node';
27
+ return 'unknown';
28
+ }
29
+ /** 当前运行时是否有可用的全局 `fetch`(Node ≥ 18 / 现代浏览器都有)。 */
30
+ export function hasGlobalFetch() {
31
+ return typeof globalThis.fetch === 'function';
32
+ }
33
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/core/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,GAAG,UAIT,CAAC;IACF,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,MAAM,KAAK,WAAW,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,WAAW,CAAC;IACvF,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IACjE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAQ,UAAkC,CAAC,KAAK,KAAK,UAAU,CAAC;AACzE,CAAC"}
@@ -0,0 +1,54 @@
1
+ export type ParamValue = string | number | boolean | null | undefined;
2
+ export interface SignInput {
3
+ /** All business + common params, excluding `sign`. */
4
+ params: Record<string, ParamValue>;
5
+ appSecret: string;
6
+ }
7
+ /**
8
+ * Computes the platform `sign` parameter.
9
+ *
10
+ * Spec (apiCallDescription): take every parameter except `sign`, sort by
11
+ * parameter **name** ascending, concatenate the **values** in that order, then
12
+ * append `appSecret` and MD5 the resulting UTF-8 string.
13
+ *
14
+ * Two details that bite in practice and are handled here:
15
+ * - sorting is by key name, not by key=value pairs;
16
+ * - `null` / `undefined` params are dropped, but empty strings are kept
17
+ * (they are real values, e.g. an explicitly blank `room` filter).
18
+ */
19
+ export declare function signParams({ params, appSecret }: SignInput): string;
20
+ export declare function stringifyParam(value: ParamValue): string;
21
+ /**
22
+ * Builds the full, signed, form-urlencoded body for `POST /invoke/router.as`.
23
+ * `timestamp` is generated in local time as `yyyyMMddHHmmss` unless supplied.
24
+ */
25
+ export declare function buildSignedRequest(args: {
26
+ appKey: string;
27
+ appSecret: string;
28
+ method: string;
29
+ accessToken: string;
30
+ params?: Record<string, ParamValue>;
31
+ timestamp?: string;
32
+ now?: Date;
33
+ }): {
34
+ body: string;
35
+ params: Record<string, string>;
36
+ sign: string;
37
+ timestamp: string;
38
+ };
39
+ /** `application/x-www-form-urlencoded` serializer matching Java/`querystring`. */
40
+ export declare function encodeForm(params: Record<string, string>): string;
41
+ /** OAuth2 `client_secret` for the token endpoint: MD5(appKey + grant + redirect + code + secret). */
42
+ export declare function signAuthCodeSecret(args: {
43
+ appKey: string;
44
+ appSecret: string;
45
+ redirectUri: string;
46
+ code: string;
47
+ }): string;
48
+ /** OAuth2 `client_secret` for the refresh endpoint: MD5(appKey + grant + redirect + refreshToken + secret). */
49
+ export declare function signRefreshSecret(args: {
50
+ appKey: string;
51
+ appSecret: string;
52
+ redirectUri: string;
53
+ refreshToken: string;
54
+ }): string;
@@ -0,0 +1,67 @@
1
+ import { formatTimestamp, md5 } from './crypto.js';
2
+ /**
3
+ * Computes the platform `sign` parameter.
4
+ *
5
+ * Spec (apiCallDescription): take every parameter except `sign`, sort by
6
+ * parameter **name** ascending, concatenate the **values** in that order, then
7
+ * append `appSecret` and MD5 the resulting UTF-8 string.
8
+ *
9
+ * Two details that bite in practice and are handled here:
10
+ * - sorting is by key name, not by key=value pairs;
11
+ * - `null` / `undefined` params are dropped, but empty strings are kept
12
+ * (they are real values, e.g. an explicitly blank `room` filter).
13
+ */
14
+ export function signParams({ params, appSecret }) {
15
+ const keys = Object.keys(params)
16
+ .filter((k) => k !== 'sign' && params[k] !== null && params[k] !== undefined)
17
+ .sort();
18
+ let raw = '';
19
+ for (const key of keys)
20
+ raw += stringifyParam(params[key]);
21
+ return md5(raw + appSecret);
22
+ }
23
+ export function stringifyParam(value) {
24
+ if (value === null || value === undefined)
25
+ return '';
26
+ if (typeof value === 'boolean')
27
+ return value ? 'true' : 'false';
28
+ return String(value);
29
+ }
30
+ /**
31
+ * Builds the full, signed, form-urlencoded body for `POST /invoke/router.as`.
32
+ * `timestamp` is generated in local time as `yyyyMMddHHmmss` unless supplied.
33
+ */
34
+ export function buildSignedRequest(args) {
35
+ const { appKey, appSecret, method, accessToken, params = {} } = args;
36
+ const timestamp = args.timestamp ?? formatTimestamp(args.now ?? new Date());
37
+ const merged = {
38
+ ...params,
39
+ client_id: appKey,
40
+ method,
41
+ access_token: accessToken,
42
+ timestamp,
43
+ };
44
+ const sign = signParams({ params: merged, appSecret });
45
+ const finalParams = { sign };
46
+ for (const [k, v] of Object.entries(merged)) {
47
+ if (v === null || v === undefined)
48
+ continue;
49
+ finalParams[k] = stringifyParam(v);
50
+ }
51
+ return { body: encodeForm(finalParams), params: finalParams, sign, timestamp };
52
+ }
53
+ /** `application/x-www-form-urlencoded` serializer matching Java/`querystring`. */
54
+ export function encodeForm(params) {
55
+ return Object.keys(params)
56
+ .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
57
+ .join('&');
58
+ }
59
+ /** OAuth2 `client_secret` for the token endpoint: MD5(appKey + grant + redirect + code + secret). */
60
+ export function signAuthCodeSecret(args) {
61
+ return md5(`${args.appKey}authorization_code${args.redirectUri}${args.code}${args.appSecret}`);
62
+ }
63
+ /** OAuth2 `client_secret` for the refresh endpoint: MD5(appKey + grant + redirect + refreshToken + secret). */
64
+ export function signRefreshSecret(args) {
65
+ return md5(`${args.appKey}refresh_token${args.redirectUri}${args.refreshToken}${args.appSecret}`);
66
+ }
67
+ //# sourceMappingURL=sign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.js","sourceRoot":"","sources":["../../../src/core/sign.ts"],"names":[],"mappings":"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"}