@mars-sea/dsh-commandcode-provider 0.7.1 → 0.8.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.
- package/CHANGELOG.md +7 -0
- package/README.md +4 -1
- package/README.zh-CN.md +4 -1
- package/lib/client.js +690 -15
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +197 -1
- package/lib/index.js +509 -13
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -358,6 +358,14 @@ window.__ModuleLoader__.load({
|
|
|
358
358
|
this.failed = false;
|
|
359
359
|
this.publish();
|
|
360
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Re-read the Host's credential facts without any staged edit. The browser
|
|
363
|
+
* login stores a key Host-side behind the page's back; the plugin entry
|
|
364
|
+
* calls this when a login lands so the configured/writable badges follow.
|
|
365
|
+
*/
|
|
366
|
+
refreshCredentials() {
|
|
367
|
+
this.describeAll();
|
|
368
|
+
}
|
|
361
369
|
/** Write every staged edit, then re-read the Host's accepted state. */
|
|
362
370
|
async save() {
|
|
363
371
|
const plan = this.plan();
|
|
@@ -749,36 +757,256 @@ window.__ModuleLoader__.load({
|
|
|
749
757
|
return new Date(ms).toLocaleString();
|
|
750
758
|
}
|
|
751
759
|
//#endregion
|
|
760
|
+
//#region src/client/login.ts
|
|
761
|
+
/** How often a live attempt is polled. */
|
|
762
|
+
const POLL_INTERVAL_MS = 1e3;
|
|
763
|
+
/**
|
|
764
|
+
* The login panel's fetch/poll lifecycle. One poll loop at a time; a fresh
|
|
765
|
+
* `begin()` supersedes any previous loop via a generation token.
|
|
766
|
+
*/
|
|
767
|
+
var CommandCodeLoginController = class {
|
|
768
|
+
remote;
|
|
769
|
+
listeners = /* @__PURE__ */ new Set();
|
|
770
|
+
pollMs;
|
|
771
|
+
/** Monotonic token; only the latest loop may publish polling results. */
|
|
772
|
+
generation = 0;
|
|
773
|
+
disposed = false;
|
|
774
|
+
phase = "idle";
|
|
775
|
+
authUrl;
|
|
776
|
+
userName;
|
|
777
|
+
keyName;
|
|
778
|
+
reason;
|
|
779
|
+
message;
|
|
780
|
+
constructor(remote, pollMs = POLL_INTERVAL_MS) {
|
|
781
|
+
this.remote = remote;
|
|
782
|
+
this.pollMs = pollMs;
|
|
783
|
+
}
|
|
784
|
+
/** Subscribe to state projections. @returns the disposer. */
|
|
785
|
+
subscribe(listener) {
|
|
786
|
+
this.listeners.add(listener);
|
|
787
|
+
return () => this.listeners.delete(listener);
|
|
788
|
+
}
|
|
789
|
+
/** Build the current panel state face. */
|
|
790
|
+
state() {
|
|
791
|
+
return {
|
|
792
|
+
phase: this.phase,
|
|
793
|
+
authUrl: this.authUrl,
|
|
794
|
+
userName: this.userName,
|
|
795
|
+
keyName: this.keyName,
|
|
796
|
+
reason: this.reason,
|
|
797
|
+
message: this.message
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/** Start (or rejoin) a login attempt and begin polling its status. */
|
|
801
|
+
async begin() {
|
|
802
|
+
if (this.disposed || this.phase === "starting" || this.phase === "waiting") return;
|
|
803
|
+
const generation = ++this.generation;
|
|
804
|
+
this.set({
|
|
805
|
+
phase: "starting",
|
|
806
|
+
authUrl: void 0,
|
|
807
|
+
userName: void 0,
|
|
808
|
+
keyName: void 0,
|
|
809
|
+
reason: void 0,
|
|
810
|
+
message: void 0
|
|
811
|
+
});
|
|
812
|
+
const remote = this.remote();
|
|
813
|
+
if (remote === void 0) {
|
|
814
|
+
this.set({
|
|
815
|
+
phase: "unavailable",
|
|
816
|
+
authUrl: void 0,
|
|
817
|
+
userName: void 0,
|
|
818
|
+
keyName: void 0,
|
|
819
|
+
reason: void 0,
|
|
820
|
+
message: "login remote is not mounted"
|
|
821
|
+
});
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
let result;
|
|
825
|
+
try {
|
|
826
|
+
result = await remote.loginBegin();
|
|
827
|
+
} catch (error) {
|
|
828
|
+
result = {
|
|
829
|
+
ok: false,
|
|
830
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
if (this.superseded(generation)) return;
|
|
834
|
+
if (!result.ok) {
|
|
835
|
+
this.set({
|
|
836
|
+
phase: "unavailable",
|
|
837
|
+
authUrl: void 0,
|
|
838
|
+
userName: void 0,
|
|
839
|
+
keyName: void 0,
|
|
840
|
+
reason: void 0,
|
|
841
|
+
message: result.error.message
|
|
842
|
+
});
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
this.apply(result.value);
|
|
846
|
+
if (this.currentPhase === "waiting") this.poll(generation);
|
|
847
|
+
}
|
|
848
|
+
/** Cancel a waiting attempt. */
|
|
849
|
+
async cancel() {
|
|
850
|
+
if (this.disposed || this.phase !== "starting" && this.phase !== "waiting") return;
|
|
851
|
+
const generation = ++this.generation;
|
|
852
|
+
const remote = this.remote();
|
|
853
|
+
if (remote === void 0) return;
|
|
854
|
+
let result;
|
|
855
|
+
try {
|
|
856
|
+
result = await remote.loginCancel();
|
|
857
|
+
} catch {
|
|
858
|
+
this.set({
|
|
859
|
+
phase: "failed",
|
|
860
|
+
authUrl: void 0,
|
|
861
|
+
userName: void 0,
|
|
862
|
+
keyName: void 0,
|
|
863
|
+
reason: "cancelled",
|
|
864
|
+
message: void 0
|
|
865
|
+
});
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (this.superseded(generation)) return;
|
|
869
|
+
if (result.ok) this.apply(result.value);
|
|
870
|
+
else this.set({
|
|
871
|
+
phase: "failed",
|
|
872
|
+
authUrl: void 0,
|
|
873
|
+
userName: void 0,
|
|
874
|
+
keyName: void 0,
|
|
875
|
+
reason: "cancelled",
|
|
876
|
+
message: void 0
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
/** Stop polling and release listeners. Idempotent. */
|
|
880
|
+
dispose() {
|
|
881
|
+
if (this.disposed) return;
|
|
882
|
+
this.disposed = true;
|
|
883
|
+
this.generation += 1;
|
|
884
|
+
this.listeners.clear();
|
|
885
|
+
}
|
|
886
|
+
/** Poll until the attempt leaves `waiting` or a newer loop supersedes us. */
|
|
887
|
+
async poll(generation) {
|
|
888
|
+
while (!this.disposed && !this.superseded(generation) && this.currentPhase === "waiting") {
|
|
889
|
+
await sleep(this.pollMs);
|
|
890
|
+
if (this.disposed || this.superseded(generation) || this.currentPhase !== "waiting") return;
|
|
891
|
+
const remote = this.remote();
|
|
892
|
+
if (remote === void 0) {
|
|
893
|
+
this.set({
|
|
894
|
+
phase: "unavailable",
|
|
895
|
+
authUrl: void 0,
|
|
896
|
+
userName: void 0,
|
|
897
|
+
keyName: void 0,
|
|
898
|
+
reason: void 0,
|
|
899
|
+
message: "login remote is not mounted"
|
|
900
|
+
});
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
let result;
|
|
904
|
+
try {
|
|
905
|
+
result = await remote.loginStatus();
|
|
906
|
+
} catch {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (this.superseded(generation) || this.currentPhase !== "waiting") return;
|
|
910
|
+
if (result.ok) this.apply(result.value);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
get currentPhase() {
|
|
914
|
+
return this.phase;
|
|
915
|
+
}
|
|
916
|
+
/** Project one Host status onto the panel face. */
|
|
917
|
+
apply(status) {
|
|
918
|
+
const base = {
|
|
919
|
+
authUrl: void 0,
|
|
920
|
+
userName: void 0,
|
|
921
|
+
keyName: void 0,
|
|
922
|
+
reason: void 0,
|
|
923
|
+
message: void 0
|
|
924
|
+
};
|
|
925
|
+
if (status.state === "waiting") {
|
|
926
|
+
this.set({
|
|
927
|
+
...base,
|
|
928
|
+
phase: "waiting",
|
|
929
|
+
authUrl: status.authUrl
|
|
930
|
+
});
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (status.state === "success") {
|
|
934
|
+
this.set({
|
|
935
|
+
...base,
|
|
936
|
+
phase: "success",
|
|
937
|
+
userName: status.userName,
|
|
938
|
+
keyName: status.keyName
|
|
939
|
+
});
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if (status.state === "failed") {
|
|
943
|
+
this.set({
|
|
944
|
+
...base,
|
|
945
|
+
phase: "failed",
|
|
946
|
+
reason: status.reason,
|
|
947
|
+
message: status.message
|
|
948
|
+
});
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
this.set({
|
|
952
|
+
...base,
|
|
953
|
+
phase: "failed",
|
|
954
|
+
reason: "cancelled",
|
|
955
|
+
message: "the login attempt is no longer active"
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
/** Replace the whole state face and notify. Explicit over partial patches. */
|
|
959
|
+
set(state) {
|
|
960
|
+
this.phase = state.phase;
|
|
961
|
+
this.authUrl = state.authUrl;
|
|
962
|
+
this.userName = state.userName;
|
|
963
|
+
this.keyName = state.keyName;
|
|
964
|
+
this.reason = state.reason;
|
|
965
|
+
this.message = state.message;
|
|
966
|
+
this.publish();
|
|
967
|
+
}
|
|
968
|
+
superseded(generation) {
|
|
969
|
+
return this.disposed || generation !== this.generation;
|
|
970
|
+
}
|
|
971
|
+
publish() {
|
|
972
|
+
if (this.disposed) return;
|
|
973
|
+
for (const listener of [...this.listeners]) listener();
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
function sleep(ms) {
|
|
977
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
978
|
+
}
|
|
979
|
+
//#endregion
|
|
752
980
|
//#region src/usage-wire.ts
|
|
753
981
|
/** The npm package identity both contribution registrations claim. */
|
|
754
982
|
const USAGE_REMOTE_PACKAGE = "@mars-sea/dsh-commandcode-provider";
|
|
755
983
|
/** Canonical `<namespace>/<method>` endpoint of the usage report Remote. */
|
|
756
984
|
const USAGE_REPORT_ENDPOINT = "commandcode/report";
|
|
757
985
|
/** Reject one boundary value with a field-naming error. */
|
|
758
|
-
function reject(field) {
|
|
986
|
+
function reject$1(field) {
|
|
759
987
|
throw new TypeError(`commandcode/report result: invalid ${field}`);
|
|
760
988
|
}
|
|
761
989
|
/** Read one required finite number field (`field` is the dotted error label). */
|
|
762
990
|
function numberField(source, key, field) {
|
|
763
991
|
const value = source[key];
|
|
764
|
-
if (typeof value !== "number" || !Number.isFinite(value)) reject(field);
|
|
992
|
+
if (typeof value !== "number" || !Number.isFinite(value)) reject$1(field);
|
|
765
993
|
return value;
|
|
766
994
|
}
|
|
767
995
|
/** Read one required string field (`field` is the dotted error label). */
|
|
768
996
|
function stringField(source, key, field) {
|
|
769
997
|
const value = source[key];
|
|
770
|
-
if (typeof value !== "string") reject(field);
|
|
998
|
+
if (typeof value !== "string") reject$1(field);
|
|
771
999
|
return value;
|
|
772
1000
|
}
|
|
773
1001
|
/** Read one required boolean field (`field` is the dotted error label). */
|
|
774
1002
|
function booleanField(source, key, field) {
|
|
775
1003
|
const value = source[key];
|
|
776
|
-
if (typeof value !== "boolean") reject(field);
|
|
1004
|
+
if (typeof value !== "boolean") reject$1(field);
|
|
777
1005
|
return value;
|
|
778
1006
|
}
|
|
779
1007
|
/** Narrow an unknown value to a plain record, or reject. */
|
|
780
1008
|
function record(value, field) {
|
|
781
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) reject(field);
|
|
1009
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) reject$1(field);
|
|
782
1010
|
return value;
|
|
783
1011
|
}
|
|
784
1012
|
/** Validate one window-limit block (`fiveHour` / `weekly`). */
|
|
@@ -799,11 +1027,11 @@ window.__ModuleLoader__.load({
|
|
|
799
1027
|
function parseUsageReport(value) {
|
|
800
1028
|
const source = record(value, "report");
|
|
801
1029
|
const failures = source.failures;
|
|
802
|
-
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject("failures");
|
|
1030
|
+
if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$1("failures");
|
|
803
1031
|
const report = { failures };
|
|
804
1032
|
if (source.blocked !== void 0) {
|
|
805
1033
|
const blocked = source.blocked;
|
|
806
|
-
if (blocked !== "invalid-key" && blocked !== "service-unavailable" && blocked !== "network") reject("blocked");
|
|
1034
|
+
if (blocked !== "invalid-key" && blocked !== "service-unavailable" && blocked !== "network") reject$1("blocked");
|
|
807
1035
|
report.blocked = blocked;
|
|
808
1036
|
}
|
|
809
1037
|
if (source.account !== void 0) {
|
|
@@ -841,7 +1069,7 @@ window.__ModuleLoader__.load({
|
|
|
841
1069
|
if (source.plan !== void 0) {
|
|
842
1070
|
const plan = record(source.plan, "plan");
|
|
843
1071
|
const monthly = plan.monthlyCredits;
|
|
844
|
-
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject("plan.monthlyCredits");
|
|
1072
|
+
if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$1("plan.monthlyCredits");
|
|
845
1073
|
report.plan = {
|
|
846
1074
|
planId: stringField(plan, "planId", "plan.planId"),
|
|
847
1075
|
name: stringField(plan, "name", "plan.name"),
|
|
@@ -868,7 +1096,7 @@ window.__ModuleLoader__.load({
|
|
|
868
1096
|
/** Parse the wire result into a {@link CommandCodeAccountsReport}. */
|
|
869
1097
|
function parseAccountsReport(value) {
|
|
870
1098
|
const accounts = record(value, "result").accounts;
|
|
871
|
-
if (!Array.isArray(accounts)) reject("accounts");
|
|
1099
|
+
if (!Array.isArray(accounts)) reject$1("accounts");
|
|
872
1100
|
return { accounts: accounts.map(parseAccountUsage) };
|
|
873
1101
|
}
|
|
874
1102
|
/** The Client-face contribution mounted on `ctx.remote`. */
|
|
@@ -889,6 +1117,92 @@ window.__ModuleLoader__.load({
|
|
|
889
1117
|
}]
|
|
890
1118
|
};
|
|
891
1119
|
//#endregion
|
|
1120
|
+
//#region src/login-wire.ts
|
|
1121
|
+
/** The canonical endpoint paths of the three login Remotes. */
|
|
1122
|
+
const LOGIN_BEGIN_ENDPOINT = "commandcode/loginBegin";
|
|
1123
|
+
const LOGIN_STATUS_ENDPOINT = "commandcode/loginStatus";
|
|
1124
|
+
const LOGIN_CANCEL_ENDPOINT = "commandcode/loginCancel";
|
|
1125
|
+
const REASONS = [
|
|
1126
|
+
"denied",
|
|
1127
|
+
"timeout",
|
|
1128
|
+
"invalid-key",
|
|
1129
|
+
"network",
|
|
1130
|
+
"unavailable",
|
|
1131
|
+
"cancelled",
|
|
1132
|
+
"error"
|
|
1133
|
+
];
|
|
1134
|
+
/** Reject one boundary value with a field-naming error. */
|
|
1135
|
+
function reject(field) {
|
|
1136
|
+
throw new TypeError(`commandcode/login result: invalid ${field}`);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Parse one untrusted boundary value into a {@link CommandCodeLoginStatus}.
|
|
1140
|
+
* Every field is shape-checked so a malformed frame fails the boundary
|
|
1141
|
+
* instead of leaking into the page.
|
|
1142
|
+
*/
|
|
1143
|
+
function parseLoginStatus(value) {
|
|
1144
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) reject("status");
|
|
1145
|
+
const source = value;
|
|
1146
|
+
const state = source.state;
|
|
1147
|
+
if (state !== "idle" && state !== "waiting" && state !== "success" && state !== "failed") reject("state");
|
|
1148
|
+
const status = { state };
|
|
1149
|
+
if (source.authUrl !== void 0) {
|
|
1150
|
+
if (typeof source.authUrl !== "string") reject("authUrl");
|
|
1151
|
+
status.authUrl = source.authUrl;
|
|
1152
|
+
}
|
|
1153
|
+
if (source.userName !== void 0) {
|
|
1154
|
+
if (typeof source.userName !== "string") reject("userName");
|
|
1155
|
+
status.userName = source.userName;
|
|
1156
|
+
}
|
|
1157
|
+
if (source.keyName !== void 0) {
|
|
1158
|
+
if (typeof source.keyName !== "string") reject("keyName");
|
|
1159
|
+
status.keyName = source.keyName;
|
|
1160
|
+
}
|
|
1161
|
+
if (source.reason !== void 0) {
|
|
1162
|
+
if (!REASONS.includes(source.reason)) reject("reason");
|
|
1163
|
+
status.reason = source.reason;
|
|
1164
|
+
}
|
|
1165
|
+
if (source.message !== void 0) {
|
|
1166
|
+
if (typeof source.message !== "string") reject("message");
|
|
1167
|
+
status.message = source.message;
|
|
1168
|
+
}
|
|
1169
|
+
return status;
|
|
1170
|
+
}
|
|
1171
|
+
/** The strict result codec shared by all three login endpoints. */
|
|
1172
|
+
const loginStatusSchema = { parse: parseLoginStatus };
|
|
1173
|
+
/** Build one login invocation descriptor (uniform result, no parameters). */
|
|
1174
|
+
function loginDescriptor(endpoint, method) {
|
|
1175
|
+
return {
|
|
1176
|
+
id: `${USAGE_REMOTE_PACKAGE}#${endpoint}`,
|
|
1177
|
+
service: "commandcodeUsage",
|
|
1178
|
+
namespace: "commandcode",
|
|
1179
|
+
method,
|
|
1180
|
+
invocation: { kind: "direct" },
|
|
1181
|
+
parameters: [],
|
|
1182
|
+
result: {
|
|
1183
|
+
mode: "strict",
|
|
1184
|
+
typeSymbol: `${USAGE_REMOTE_PACKAGE}#CommandCodeLoginStatus`,
|
|
1185
|
+
schema: loginStatusSchema
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
/** The Client-face contribution fragment mounted on `ctx.remote`. */
|
|
1190
|
+
const LOGIN_REMOTE_CONTRIBUTION = {
|
|
1191
|
+
package: USAGE_REMOTE_PACKAGE,
|
|
1192
|
+
descriptors: [
|
|
1193
|
+
loginDescriptor(LOGIN_BEGIN_ENDPOINT, "loginBegin"),
|
|
1194
|
+
loginDescriptor(LOGIN_STATUS_ENDPOINT, "loginStatus"),
|
|
1195
|
+
loginDescriptor(LOGIN_CANCEL_ENDPOINT, "loginCancel")
|
|
1196
|
+
]
|
|
1197
|
+
};
|
|
1198
|
+
//#endregion
|
|
1199
|
+
//#region package.json
|
|
1200
|
+
var version = "0.8.0";
|
|
1201
|
+
var repository = {
|
|
1202
|
+
"type": "git",
|
|
1203
|
+
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
1204
|
+
};
|
|
1205
|
+
//#endregion
|
|
892
1206
|
//#region src/client/version.ts
|
|
893
1207
|
/**
|
|
894
1208
|
* The plugin's own version, read from package.json at build time.
|
|
@@ -902,7 +1216,154 @@ window.__ModuleLoader__.load({
|
|
|
902
1216
|
* @module dsh-commandcode-provider/client/version
|
|
903
1217
|
*/
|
|
904
1218
|
/** The published package version (e.g. `'0.6.0'`). */
|
|
905
|
-
const PLUGIN_VERSION =
|
|
1219
|
+
const PLUGIN_VERSION = version;
|
|
1220
|
+
/**
|
|
1221
|
+
* This package's GitHub releases page, derived from the repository field so
|
|
1222
|
+
* the update hint's link target can never drift from the published home.
|
|
1223
|
+
*/
|
|
1224
|
+
const PLUGIN_RELEASES_URL = `${repository.url.replace(/^git\+/, "").replace(/\.git$/, "")}/releases`;
|
|
1225
|
+
/** Abort a hung registry request rather than keep the footer waiting. */
|
|
1226
|
+
const FETCH_TIMEOUT_MS = 5e3;
|
|
1227
|
+
/**
|
|
1228
|
+
* The npm registry document for this package's `latest` dist-tag. The scoped
|
|
1229
|
+
* name is path-escaped (`%2F`) so no client normalizes the slash away.
|
|
1230
|
+
*/
|
|
1231
|
+
const NPM_LATEST_URL = "https://registry.npmjs.org/@mars-sea%2Fdsh-commandcode-provider/latest";
|
|
1232
|
+
/**
|
|
1233
|
+
* Compare two version strings (`major.minor.patch[-pre]`). Returns a negative
|
|
1234
|
+
* number when `a` sorts before `b`, positive when after, zero when equal.
|
|
1235
|
+
*
|
|
1236
|
+
* Tolerant by design: a leading `v` is stripped, unparsable numeric parts
|
|
1237
|
+
* count as `0`, and semver prerelease rules apply (release > prerelease;
|
|
1238
|
+
* numeric identifiers compare numerically, everything else lexically, a
|
|
1239
|
+
* shorter identifier list sorts first). Enough for release tags; not a full
|
|
1240
|
+
* semver validator.
|
|
1241
|
+
*/
|
|
1242
|
+
function compareVersions(a, b) {
|
|
1243
|
+
const left = splitVersion(a);
|
|
1244
|
+
const right = splitVersion(b);
|
|
1245
|
+
const depth = Math.max(left.core.length, right.core.length);
|
|
1246
|
+
for (let index = 0; index < depth; index += 1) {
|
|
1247
|
+
const delta = (left.core[index] ?? 0) - (right.core[index] ?? 0);
|
|
1248
|
+
if (delta !== 0) return Math.sign(delta);
|
|
1249
|
+
}
|
|
1250
|
+
if (left.pre.length === 0 && right.pre.length === 0) return 0;
|
|
1251
|
+
if (left.pre.length === 0) return 1;
|
|
1252
|
+
if (right.pre.length === 0) return -1;
|
|
1253
|
+
const width = Math.max(left.pre.length, right.pre.length);
|
|
1254
|
+
for (let index = 0; index < width; index += 1) {
|
|
1255
|
+
const l = left.pre[index];
|
|
1256
|
+
const r = right.pre[index];
|
|
1257
|
+
if (l === void 0) return -1;
|
|
1258
|
+
if (r === void 0) return 1;
|
|
1259
|
+
const lNumeric = /^\d+$/.test(l);
|
|
1260
|
+
const rNumeric = /^\d+$/.test(r);
|
|
1261
|
+
let delta;
|
|
1262
|
+
if (lNumeric && rNumeric) delta = Number(l) - Number(r);
|
|
1263
|
+
else if (lNumeric) delta = -1;
|
|
1264
|
+
else if (rNumeric) delta = 1;
|
|
1265
|
+
else delta = l < r ? -1 : l > r ? 1 : 0;
|
|
1266
|
+
if (delta !== 0) return Math.sign(delta);
|
|
1267
|
+
}
|
|
1268
|
+
return 0;
|
|
1269
|
+
}
|
|
1270
|
+
/** True when `candidate` is strictly newer than `current`. */
|
|
1271
|
+
function isNewerVersion(candidate, current) {
|
|
1272
|
+
return compareVersions(candidate, current) > 0;
|
|
1273
|
+
}
|
|
1274
|
+
/** Split a tolerant version string into numeric core + prerelease ids. */
|
|
1275
|
+
function splitVersion(value) {
|
|
1276
|
+
const [coreText = "", preText] = value.trim().replace(/^v/i, "").split("-");
|
|
1277
|
+
return {
|
|
1278
|
+
core: coreText === "" ? [0] : coreText.split(".").map((part) => {
|
|
1279
|
+
const parsed = Number.parseInt(part, 10);
|
|
1280
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1281
|
+
}),
|
|
1282
|
+
pre: preText === void 0 ? [] : preText.split(".")
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Extract the published version from the registry's `/latest` manifest
|
|
1287
|
+
* (`{ name, version, … }`). Throws on anything unexpected so callers treat a
|
|
1288
|
+
* shape change as a failed attempt, never as bogus data.
|
|
1289
|
+
*/
|
|
1290
|
+
function parseLatestVersion(payload) {
|
|
1291
|
+
if (typeof payload !== "object" || payload === null) throw new Error("npm latest payload is not an object");
|
|
1292
|
+
const version = payload.version;
|
|
1293
|
+
if (typeof version !== "string" || !/^\d+\.\d+\./.test(version)) throw new Error("npm latest payload has no usable version");
|
|
1294
|
+
return version;
|
|
1295
|
+
}
|
|
1296
|
+
/** Fetch and parse the published `latest` version. Rejects on any failure. */
|
|
1297
|
+
async function fetchLatestVersion(fetchImpl = fetch) {
|
|
1298
|
+
const controller = new AbortController();
|
|
1299
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1300
|
+
try {
|
|
1301
|
+
const response = await fetchImpl(NPM_LATEST_URL, { signal: controller.signal });
|
|
1302
|
+
if (!response.ok) throw new Error(`registry responded ${response.status}`);
|
|
1303
|
+
return parseLatestVersion(await response.json());
|
|
1304
|
+
} finally {
|
|
1305
|
+
clearTimeout(timer);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
/** The `localStorage` key holding {@link UpdateCheckRecord}. */
|
|
1309
|
+
const UPDATE_CHECK_CACHE_KEY = "@mars-sea/dsh-commandcode-provider/update-check";
|
|
1310
|
+
/**
|
|
1311
|
+
* A {@link UpdateCheckStore} backed by `localStorage`. Tolerates a missing or
|
|
1312
|
+
* throwing storage (SSR-ish contexts, private modes): reads yield `undefined`,
|
|
1313
|
+
* writes are dropped.
|
|
1314
|
+
*/
|
|
1315
|
+
function localStorageUpdateStore(storage = typeof localStorage === "undefined" ? void 0 : localStorage) {
|
|
1316
|
+
return {
|
|
1317
|
+
read() {
|
|
1318
|
+
if (storage === void 0) return void 0;
|
|
1319
|
+
try {
|
|
1320
|
+
const raw = storage.getItem(UPDATE_CHECK_CACHE_KEY);
|
|
1321
|
+
if (raw === null) return void 0;
|
|
1322
|
+
const parsed = JSON.parse(raw);
|
|
1323
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1324
|
+
const at = parsed.at;
|
|
1325
|
+
if (typeof at !== "number" || !Number.isFinite(at)) return void 0;
|
|
1326
|
+
const version = parsed.version;
|
|
1327
|
+
return {
|
|
1328
|
+
at,
|
|
1329
|
+
version: typeof version === "string" && version !== "" ? version : void 0
|
|
1330
|
+
};
|
|
1331
|
+
} catch {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
write(record) {
|
|
1336
|
+
if (storage === void 0) return;
|
|
1337
|
+
try {
|
|
1338
|
+
storage.setItem(UPDATE_CHECK_CACHE_KEY, JSON.stringify(record));
|
|
1339
|
+
} catch {}
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Run one throttled update check. Resolves with the newest published version
|
|
1345
|
+
* when it is newer than `currentVersion`, otherwise `undefined`.
|
|
1346
|
+
*
|
|
1347
|
+
* Within the throttle window (or on failure) the cached version answers, so
|
|
1348
|
+
* the hint keeps working offline; past the window the registry is consulted
|
|
1349
|
+
* again and the attempt time is refreshed either way.
|
|
1350
|
+
*/
|
|
1351
|
+
async function checkForUpdate(options) {
|
|
1352
|
+
const { currentVersion, now, store } = options;
|
|
1353
|
+
const hintOf = (version) => version !== void 0 && isNewerVersion(version, currentVersion) ? version : void 0;
|
|
1354
|
+
const cache = store.read();
|
|
1355
|
+
if (cache !== void 0 && now - cache.at < 864e5) return hintOf(cache.version);
|
|
1356
|
+
let learned;
|
|
1357
|
+
try {
|
|
1358
|
+
learned = await fetchLatestVersion(options.fetchImpl);
|
|
1359
|
+
} catch {}
|
|
1360
|
+
const version = learned ?? cache?.version;
|
|
1361
|
+
store.write({
|
|
1362
|
+
at: now,
|
|
1363
|
+
version
|
|
1364
|
+
});
|
|
1365
|
+
return hintOf(version);
|
|
1366
|
+
}
|
|
906
1367
|
//#endregion
|
|
907
1368
|
//#region src/client/section.tsx
|
|
908
1369
|
/**
|
|
@@ -1074,6 +1535,82 @@ window.__ModuleLoader__.load({
|
|
|
1074
1535
|
]
|
|
1075
1536
|
});
|
|
1076
1537
|
}
|
|
1538
|
+
/** The per-reason copy for a failed login attempt. */
|
|
1539
|
+
function loginFailureCopy(reason, t) {
|
|
1540
|
+
if (reason === "denied") return t("loginDenied");
|
|
1541
|
+
if (reason === "timeout") return t("loginTimeout");
|
|
1542
|
+
if (reason === "invalid-key") return t("loginInvalidKey");
|
|
1543
|
+
if (reason === "network") return t("loginNetwork");
|
|
1544
|
+
if (reason === "unavailable") return t("loginStoreFailed");
|
|
1545
|
+
if (reason === "cancelled") return t("loginCancelled");
|
|
1546
|
+
return t("loginFailedGeneric");
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* The sign-in alternative to pasting a key: one field row that starts the
|
|
1550
|
+
* Host-side browser login, links to the Studio authorization page while the
|
|
1551
|
+
* attempt is live, and reports the outcome. The key itself never crosses to
|
|
1552
|
+
* the browser — only "who signed in" does.
|
|
1553
|
+
*/
|
|
1554
|
+
function LoginPanel({ state, disabled, t, onBegin, onCancel }) {
|
|
1555
|
+
const busy = state.phase === "starting" || state.phase === "waiting";
|
|
1556
|
+
let hint = t("loginHintIdle");
|
|
1557
|
+
let hintTitle;
|
|
1558
|
+
let hintClass = "cc-hint";
|
|
1559
|
+
if (state.phase === "starting" || state.phase === "waiting") hint = t(state.phase === "starting" ? "loginStarting" : "loginWaiting");
|
|
1560
|
+
else if (state.phase === "success") {
|
|
1561
|
+
const keyName = state.keyName !== void 0 && state.keyName !== "" ? ` · ${state.keyName}` : "";
|
|
1562
|
+
hint = `${t("loginSuccess")} ${state.userName ?? ""}${keyName}`.trim();
|
|
1563
|
+
hintClass = "cc-loginDone";
|
|
1564
|
+
} else if (state.phase === "failed") {
|
|
1565
|
+
hint = loginFailureCopy(state.reason, t);
|
|
1566
|
+
hintClass = "cc-loginError";
|
|
1567
|
+
if (state.message !== void 0) hintTitle = state.message;
|
|
1568
|
+
} else if (state.phase === "unavailable") {
|
|
1569
|
+
hint = `${t("loginUnavailable")} ${state.message ?? ""}`.trim();
|
|
1570
|
+
hintClass = "cc-loginError";
|
|
1571
|
+
}
|
|
1572
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1573
|
+
className: "cc-field",
|
|
1574
|
+
children: [
|
|
1575
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1576
|
+
className: "cc-fieldHead",
|
|
1577
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1578
|
+
className: "cc-label",
|
|
1579
|
+
children: t("loginTitle")
|
|
1580
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1581
|
+
className: "cc-badges",
|
|
1582
|
+
children: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1583
|
+
type: "button",
|
|
1584
|
+
className: "cc-reset",
|
|
1585
|
+
onClick: onCancel,
|
|
1586
|
+
children: t("loginCancel")
|
|
1587
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1588
|
+
type: "button",
|
|
1589
|
+
className: "cc-reset",
|
|
1590
|
+
disabled,
|
|
1591
|
+
onClick: onBegin,
|
|
1592
|
+
children: t("loginButton")
|
|
1593
|
+
})
|
|
1594
|
+
})]
|
|
1595
|
+
}),
|
|
1596
|
+
state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1597
|
+
className: "cc-hint",
|
|
1598
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
1599
|
+
className: "cc-loginLink",
|
|
1600
|
+
href: state.authUrl,
|
|
1601
|
+
target: "_blank",
|
|
1602
|
+
rel: "noreferrer",
|
|
1603
|
+
children: t("loginOpenLink")
|
|
1604
|
+
})
|
|
1605
|
+
}) : null,
|
|
1606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1607
|
+
className: hintClass,
|
|
1608
|
+
title: hintTitle,
|
|
1609
|
+
children: hint
|
|
1610
|
+
})
|
|
1611
|
+
]
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1077
1614
|
/** One stat tile in the account card's summary grid. */
|
|
1078
1615
|
function UsageStat({ label, value, sub }) {
|
|
1079
1616
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1619,14 +2156,39 @@ window.__ModuleLoader__.load({
|
|
|
1619
2156
|
}, [tick]);
|
|
1620
2157
|
return visible;
|
|
1621
2158
|
}
|
|
2159
|
+
/**
|
|
2160
|
+
* The update hint: one throttled npm-registry check per page open (the
|
|
2161
|
+
* throttle and all failure handling live in ./update.ts). Resolves to the
|
|
2162
|
+
* newest published version when it is newer than this build, else undefined —
|
|
2163
|
+
* every failure mode degrades to no hint at all.
|
|
2164
|
+
*/
|
|
2165
|
+
function usePluginUpdate() {
|
|
2166
|
+
const [available, setAvailable] = (0, react.useState)(void 0);
|
|
2167
|
+
(0, react.useEffect)(() => {
|
|
2168
|
+
let cancelled = false;
|
|
2169
|
+
checkForUpdate({
|
|
2170
|
+
currentVersion: PLUGIN_VERSION,
|
|
2171
|
+
now: Date.now(),
|
|
2172
|
+
store: localStorageUpdateStore()
|
|
2173
|
+
}).then((version) => {
|
|
2174
|
+
if (!cancelled) setAvailable(version);
|
|
2175
|
+
}, () => {});
|
|
2176
|
+
return () => {
|
|
2177
|
+
cancelled = true;
|
|
2178
|
+
};
|
|
2179
|
+
}, []);
|
|
2180
|
+
return available;
|
|
2181
|
+
}
|
|
1622
2182
|
/** The settings page body: connection facts for the Command Code provider. */
|
|
1623
2183
|
function CommandCodeSettingsPage(props) {
|
|
1624
2184
|
const { t } = props;
|
|
1625
2185
|
const state = props.useCommandCodeSettings((snapshot) => snapshot);
|
|
1626
2186
|
const usage = props.useCommandCodeUsage((snapshot) => snapshot);
|
|
2187
|
+
const login = props.useCommandCodeLogin((snapshot) => snapshot);
|
|
1627
2188
|
const disabled = !state.writable;
|
|
1628
2189
|
const keyLocked = !state.apiKeyWritable;
|
|
1629
2190
|
const savedVisible = useSavedFlash(state.savedCount);
|
|
2191
|
+
const updateVersion = usePluginUpdate();
|
|
1630
2192
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1631
2193
|
className: "cc-section",
|
|
1632
2194
|
"aria-label": t("title"),
|
|
@@ -1686,6 +2248,13 @@ window.__ModuleLoader__.load({
|
|
|
1686
2248
|
onEdit: (text) => props.edit("apiKey", text),
|
|
1687
2249
|
onToggleClear: () => props.toggleKeyClear("default")
|
|
1688
2250
|
}),
|
|
2251
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginPanel, {
|
|
2252
|
+
state: login,
|
|
2253
|
+
disabled: disabled || keyLocked,
|
|
2254
|
+
t,
|
|
2255
|
+
onBegin: props.beginLogin,
|
|
2256
|
+
onCancel: props.cancelLogin
|
|
2257
|
+
}),
|
|
1689
2258
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1690
2259
|
id: "cc-api-base",
|
|
1691
2260
|
label: t("apiBase"),
|
|
@@ -1773,7 +2342,23 @@ window.__ModuleLoader__.load({
|
|
|
1773
2342
|
}),
|
|
1774
2343
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1775
2344
|
className: "cc-version",
|
|
1776
|
-
children: [
|
|
2345
|
+
children: [
|
|
2346
|
+
"Command Code Provider v",
|
|
2347
|
+
PLUGIN_VERSION,
|
|
2348
|
+
updateVersion !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" · ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
2349
|
+
className: "cc-versionLink",
|
|
2350
|
+
href: PLUGIN_RELEASES_URL,
|
|
2351
|
+
target: "_blank",
|
|
2352
|
+
rel: "noreferrer",
|
|
2353
|
+
title: t("updateHint"),
|
|
2354
|
+
children: [
|
|
2355
|
+
"v",
|
|
2356
|
+
updateVersion,
|
|
2357
|
+
" ",
|
|
2358
|
+
t("updateAvailable")
|
|
2359
|
+
]
|
|
2360
|
+
})] }) : null
|
|
2361
|
+
]
|
|
1777
2362
|
})
|
|
1778
2363
|
]
|
|
1779
2364
|
});
|
|
@@ -1860,7 +2445,25 @@ window.__ModuleLoader__.load({
|
|
|
1860
2445
|
usageActive: "当前使用",
|
|
1861
2446
|
usageCooldown: "限额冷却中",
|
|
1862
2447
|
usageInvalidKey: "密钥无效",
|
|
1863
|
-
usageUnconfigured: "该账户尚未配置 API 密钥。"
|
|
2448
|
+
usageUnconfigured: "该账户尚未配置 API 密钥。",
|
|
2449
|
+
updateAvailable: "可更新",
|
|
2450
|
+
updateHint: "已发布新版本,点击查看发布说明;更新插件后刷新本页,提示会自动消失。",
|
|
2451
|
+
loginTitle: "通过官方登录获取密钥",
|
|
2452
|
+
loginHintIdle: "不想手动创建密钥?点击登录后浏览器会打开 commandcode.ai 授权页,完成后密钥自动写入本机凭据服务,下次请求即生效。",
|
|
2453
|
+
loginButton: "登录 Command Code",
|
|
2454
|
+
loginStarting: "正在启动本地回调服务…",
|
|
2455
|
+
loginWaiting: "等待在浏览器中完成授权…",
|
|
2456
|
+
loginOpenLink: "打开授权页面 ↗",
|
|
2457
|
+
loginCancel: "取消登录",
|
|
2458
|
+
loginSuccess: "已登录为",
|
|
2459
|
+
loginUnavailable: "此环境暂不支持登录流程,请手动粘贴密钥。",
|
|
2460
|
+
loginDenied: "授权被拒绝。可重试,或手动粘贴密钥。",
|
|
2461
|
+
loginTimeout: "等待超时:未在窗口期内收到授权回调,请重试。",
|
|
2462
|
+
loginInvalidKey: "获取到的密钥未通过校验(401),请重试或手动粘贴。",
|
|
2463
|
+
loginNetwork: "无法连接 Command Code 服务校验密钥,请检查网络后重试。",
|
|
2464
|
+
loginStoreFailed: "密钥无法写入本机凭据服务,请手动粘贴。",
|
|
2465
|
+
loginCancelled: "登录已取消。",
|
|
2466
|
+
loginFailedGeneric: "登录失败,请重试或手动粘贴密钥。"
|
|
1864
2467
|
};
|
|
1865
2468
|
const en = {
|
|
1866
2469
|
nav: "Command Code",
|
|
@@ -1942,7 +2545,25 @@ window.__ModuleLoader__.load({
|
|
|
1942
2545
|
usageActive: "Active",
|
|
1943
2546
|
usageCooldown: "Cooling down",
|
|
1944
2547
|
usageInvalidKey: "Invalid key",
|
|
1945
|
-
usageUnconfigured: "No API key configured for this account yet."
|
|
2548
|
+
usageUnconfigured: "No API key configured for this account yet.",
|
|
2549
|
+
updateAvailable: "update available",
|
|
2550
|
+
updateHint: "A newer version has been published; click for release notes. The notice disappears once the plugin is updated.",
|
|
2551
|
+
loginTitle: "Sign in to fetch a key",
|
|
2552
|
+
loginHintIdle: "Rather not create a key by hand? Sign in and your browser opens the commandcode.ai authorization page; the approved key is stored in the local credential service and applies to the next request.",
|
|
2553
|
+
loginButton: "Sign in to Command Code",
|
|
2554
|
+
loginStarting: "Starting the local callback server…",
|
|
2555
|
+
loginWaiting: "Waiting for authorization in your browser…",
|
|
2556
|
+
loginOpenLink: "Open the authorization page ↗",
|
|
2557
|
+
loginCancel: "Cancel sign-in",
|
|
2558
|
+
loginSuccess: "Signed in as",
|
|
2559
|
+
loginUnavailable: "Sign-in is unavailable in this environment; paste the API key instead.",
|
|
2560
|
+
loginDenied: "Authorization was denied. Try again or paste the key manually.",
|
|
2561
|
+
loginTimeout: "Timed out waiting for the authorization callback; try again.",
|
|
2562
|
+
loginInvalidKey: "The delivered key failed validation (401). Try again or paste it manually.",
|
|
2563
|
+
loginNetwork: "Could not reach the Command Code service to validate the key; check your network and retry.",
|
|
2564
|
+
loginStoreFailed: "The key could not be stored in the local credential service; paste it manually.",
|
|
2565
|
+
loginCancelled: "Sign-in cancelled.",
|
|
2566
|
+
loginFailedGeneric: "Sign-in failed; try again or paste the key manually."
|
|
1946
2567
|
};
|
|
1947
2568
|
//#endregion
|
|
1948
2569
|
//#region src/client/index.ts
|
|
@@ -2027,6 +2648,17 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
2027
2648
|
.cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
2028
2649
|
.cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
|
|
2029
2650
|
.cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
2651
|
+
/* The update hint rides the footer version line: warning-tinted (with a
|
|
2652
|
+
* muted fallback for themes without the alias), quiet until hovered. */
|
|
2653
|
+
.cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
|
|
2654
|
+
.cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
|
|
2655
|
+
/* The login panel rides the connection card as one more field row; the
|
|
2656
|
+
* authorization link is the only branded element on it. */
|
|
2657
|
+
.cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
|
|
2658
|
+
.cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
|
|
2659
|
+
.cc-loginBusy{color:var(--dsw-alias-label-tertiary)}
|
|
2660
|
+
.cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
|
|
2661
|
+
.cc-loginError{color:var(--dsw-alias-label-error)}
|
|
2030
2662
|
.cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
|
|
2031
2663
|
.cc-badgeWarn{background:var(--dsw-alias-state-warning-secondary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary))}
|
|
2032
2664
|
`;
|
|
@@ -2063,10 +2695,14 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
2063
2695
|
controller.subscribe(() => store.set(controller.state()));
|
|
2064
2696
|
let usageNamespace;
|
|
2065
2697
|
let usageMountError;
|
|
2698
|
+
const contribution = {
|
|
2699
|
+
package: USAGE_REMOTE_CONTRIBUTION.package,
|
|
2700
|
+
descriptors: [...USAGE_REMOTE_CONTRIBUTION.descriptors, ...LOGIN_REMOTE_CONTRIBUTION.descriptors]
|
|
2701
|
+
};
|
|
2066
2702
|
ctx.effect(() => {
|
|
2067
2703
|
let cancelled = false;
|
|
2068
2704
|
let unmount;
|
|
2069
|
-
ctx.remote.$mount(
|
|
2705
|
+
ctx.remote.$mount(contribution).then((dispose) => {
|
|
2070
2706
|
if (cancelled) {
|
|
2071
2707
|
dispose();
|
|
2072
2708
|
return;
|
|
@@ -2098,10 +2734,47 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
2098
2734
|
ctx.effect(() => () => usageController.dispose(), "dsh-commandcode-provider: usage controller");
|
|
2099
2735
|
const usageStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(usageController.state());
|
|
2100
2736
|
usageController.subscribe(() => usageStore.set(usageController.state()));
|
|
2737
|
+
const loginRemote = {
|
|
2738
|
+
loginBegin: async () => {
|
|
2739
|
+
if (usageNamespace === void 0) return {
|
|
2740
|
+
ok: false,
|
|
2741
|
+
error: { message: usageMountError ?? "commandcode remote is not mounted" }
|
|
2742
|
+
};
|
|
2743
|
+
return usageNamespace.loginBegin();
|
|
2744
|
+
},
|
|
2745
|
+
loginStatus: async () => {
|
|
2746
|
+
if (usageNamespace === void 0) return {
|
|
2747
|
+
ok: false,
|
|
2748
|
+
error: { message: usageMountError ?? "commandcode remote is not mounted" }
|
|
2749
|
+
};
|
|
2750
|
+
return usageNamespace.loginStatus();
|
|
2751
|
+
},
|
|
2752
|
+
loginCancel: async () => {
|
|
2753
|
+
if (usageNamespace === void 0) return {
|
|
2754
|
+
ok: false,
|
|
2755
|
+
error: { message: usageMountError ?? "commandcode remote is not mounted" }
|
|
2756
|
+
};
|
|
2757
|
+
return usageNamespace.loginCancel();
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
const loginController = new CommandCodeLoginController(() => loginRemote);
|
|
2761
|
+
ctx.effect(() => () => loginController.dispose(), "dsh-commandcode-provider: login controller");
|
|
2762
|
+
const loginStore = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(loginController.state());
|
|
2763
|
+
let lastLoginPhase = loginController.state().phase;
|
|
2764
|
+
loginController.subscribe(() => {
|
|
2765
|
+
const phase = loginController.state().phase;
|
|
2766
|
+
if (phase === "success" && lastLoginPhase !== "success") {
|
|
2767
|
+
controller.refreshCredentials();
|
|
2768
|
+
usageController.refresh();
|
|
2769
|
+
}
|
|
2770
|
+
lastLoginPhase = phase;
|
|
2771
|
+
loginStore.set(loginController.state());
|
|
2772
|
+
});
|
|
2101
2773
|
const injected = () => ({
|
|
2102
2774
|
hooks: {
|
|
2103
2775
|
commandCodeSettings: store,
|
|
2104
|
-
commandCodeUsage: usageStore
|
|
2776
|
+
commandCodeUsage: usageStore,
|
|
2777
|
+
commandCodeLogin: loginStore
|
|
2105
2778
|
},
|
|
2106
2779
|
edit: (field, text) => controller.edit(field, text),
|
|
2107
2780
|
resetField: (field) => controller.resetField(field),
|
|
@@ -2111,6 +2784,8 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
2111
2784
|
}),
|
|
2112
2785
|
discard: () => controller.discard(),
|
|
2113
2786
|
refreshUsage: () => void usageController.refresh(),
|
|
2787
|
+
beginLogin: () => void loginController.begin(),
|
|
2788
|
+
cancelLogin: () => void loginController.cancel(),
|
|
2114
2789
|
addAccount: () => controller.addAccount(),
|
|
2115
2790
|
removeAccount: (id) => controller.removeAccount(id),
|
|
2116
2791
|
editAccountLabel: (id, text) => controller.editAccountLabel(id, text),
|