dsh-connect-workbuddy 4.9.3 → 4.10.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 +84 -0
- package/README.en.md +4 -3
- package/README.md +3 -2
- package/lib/bin.js +1 -1
- package/lib/client.js +248 -10
- package/lib/{host-heartbeat-BHaBzzOr.js → host-heartbeat-BQHO4oiB.js} +118 -10
- package/lib/index.d.ts +176 -1
- package/lib/index.js +223 -7
- package/package.json +2 -2
|
@@ -917,6 +917,19 @@ function clearAtRestKeyCache() {
|
|
|
917
917
|
*
|
|
918
918
|
* @module dsh-connect-workbuddy/upstream
|
|
919
919
|
*/
|
|
920
|
+
/**
|
|
921
|
+
* The write half of each campaign: the endpoint that actually grants the
|
|
922
|
+
* credits.
|
|
923
|
+
*
|
|
924
|
+
* Kept beside the status paths because they must move together: the pair is
|
|
925
|
+
* what the 「套餐与积分」 page itself uses (`xp`/`vp` in its bundle), and a
|
|
926
|
+
* campaign whose status is read from one path can only be claimed through its
|
|
927
|
+
* matching claim path. Note the same absence of a `/v2` prefix.
|
|
928
|
+
*/
|
|
929
|
+
const BENEFIT_CLAIM_PATHS = {
|
|
930
|
+
compensation: "/billing/meter/claim-compensation",
|
|
931
|
+
gift: "/billing/meter/claim-gift"
|
|
932
|
+
};
|
|
920
933
|
const CN_CHAT_BASE = "https://copilot.tencent.com";
|
|
921
934
|
const CN_BILLING_BASE = "https://www.codebuddy.cn";
|
|
922
935
|
const GLOBAL_BASE = "https://www.workbuddy.ai";
|
|
@@ -1095,6 +1108,23 @@ function classifyUpstreamError(status, body) {
|
|
|
1095
1108
|
return "client";
|
|
1096
1109
|
}
|
|
1097
1110
|
/**
|
|
1111
|
+
* Read one of the upstream's localized fields, in either shape it ships: a plain
|
|
1112
|
+
* string, or a `{ zh, en }` object.
|
|
1113
|
+
*
|
|
1114
|
+
* One reader for every such field on purpose. `displayMsg` and `displayTips`
|
|
1115
|
+
* arrive in the same two shapes from the same envelope, so parsing them with two
|
|
1116
|
+
* separate hand-rolled branches is how one of them ends up only handling the
|
|
1117
|
+
* object form (which is exactly what happened to `displayTips`: it was never
|
|
1118
|
+
* read at all, issue #78).
|
|
1119
|
+
*/
|
|
1120
|
+
function localizedText(value) {
|
|
1121
|
+
if (typeof value === "string") return value;
|
|
1122
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1123
|
+
const localized = value;
|
|
1124
|
+
const pick = localized.zh ?? localized.en;
|
|
1125
|
+
return typeof pick === "string" ? pick : void 0;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1098
1128
|
* Parse the known-good fields out of an upstream failure body. Non-JSON
|
|
1099
1129
|
* bodies (HTML error pages, empty strings) yield undefined rather than a guess.
|
|
1100
1130
|
*/
|
|
@@ -1107,19 +1137,13 @@ function parseUpstreamErrorDetail(body) {
|
|
|
1107
1137
|
}
|
|
1108
1138
|
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1109
1139
|
const raw = parsed;
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
if (typeof display === "string") displayMsg = display;
|
|
1113
|
-
else if (typeof display === "object" && display !== null) {
|
|
1114
|
-
const localized = display;
|
|
1115
|
-
const pick = localized.zh ?? localized.en;
|
|
1116
|
-
if (typeof pick === "string") displayMsg = pick;
|
|
1117
|
-
}
|
|
1118
|
-
if (displayMsg === void 0 && typeof raw.msg === "string") displayMsg = raw.msg;
|
|
1140
|
+
const displayMsg = localizedText(raw.displayMsg) ?? (typeof raw.msg === "string" ? raw.msg : void 0);
|
|
1141
|
+
const tips = localizedText(raw.displayTips);
|
|
1119
1142
|
const detail = {};
|
|
1120
1143
|
if (typeof raw.code === "number") detail.upstreamCode = raw.code;
|
|
1121
1144
|
if (typeof raw.requestId === "string") detail.requestId = raw.requestId;
|
|
1122
1145
|
if (displayMsg !== void 0) detail.displayMsg = displayMsg;
|
|
1146
|
+
if (tips !== void 0) detail.tips = tips;
|
|
1123
1147
|
return detail;
|
|
1124
1148
|
}
|
|
1125
1149
|
/**
|
|
@@ -1910,6 +1934,90 @@ var WorkBuddyUpstreamClient = class {
|
|
|
1910
1934
|
...typeof data["claim_button_text"] === "string" && data["claim_button_text"] !== "" ? { claimButtonText: data["claim_button_text"] } : {}
|
|
1911
1935
|
};
|
|
1912
1936
|
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Read every promotional benefit this account can still see, WITHOUT claiming
|
|
1939
|
+
* anything.
|
|
1940
|
+
*
|
|
1941
|
+
* The campaign list is closed and small on purpose: the gateway exposes one
|
|
1942
|
+
* status endpoint per campaign, so "discover benefits" is a fixed fan-out, not
|
|
1943
|
+
* a search. A new campaign type means a new `/billing/meter/*-status` endpoint
|
|
1944
|
+
* added here — the plugin never guesses at undocumented ones.
|
|
1945
|
+
*
|
|
1946
|
+
* Every endpoint is read in parallel because they are independent, and a
|
|
1947
|
+
* failure is per-endpoint: one campaign answering 5xx must not blank out the
|
|
1948
|
+
* other. The caller gets only what was actually read.
|
|
1949
|
+
*
|
|
1950
|
+
* Note the paths carry NO `/v2` prefix — these are not the `/v2/billing/meter`
|
|
1951
|
+
* family the credit and check-in calls use. Probing `/v2/...` answers 404, which
|
|
1952
|
+
* is how this distinction was found.
|
|
1953
|
+
*/
|
|
1954
|
+
async fetchBenefits(credential) {
|
|
1955
|
+
const read = async (kind, path) => {
|
|
1956
|
+
try {
|
|
1957
|
+
return await this.readBenefitStatus(kind, credential, path);
|
|
1958
|
+
} catch {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1962
|
+
const [compensation, gift] = await Promise.all([read("compensation", "/billing/meter/compensation-status"), read("gift", "/billing/meter/check-gift-claimed")]);
|
|
1963
|
+
return [compensation, gift].filter((benefit) => benefit !== void 0);
|
|
1964
|
+
}
|
|
1965
|
+
/** One benefit status endpoint. Throws on anything that is not a clean `code: 0`. */
|
|
1966
|
+
async readBenefitStatus(kind, credential, path) {
|
|
1967
|
+
const response = await fetch(`${billingBase(credential)}${path}`, {
|
|
1968
|
+
method: "POST",
|
|
1969
|
+
headers: billingHeaders(credential),
|
|
1970
|
+
body: "{}",
|
|
1971
|
+
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
1972
|
+
});
|
|
1973
|
+
const envelope = await readEnvelope(response);
|
|
1974
|
+
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
1975
|
+
const data = typeof envelope.data === "object" && envelope.data !== null ? envelope.data : {};
|
|
1976
|
+
const numberField = (key) => typeof data[key] === "number" ? data[key] : 0;
|
|
1977
|
+
/** The gateway states times as `YYYY-MM-DD HH:MM:SS` in local time. */
|
|
1978
|
+
const timeField = (key) => {
|
|
1979
|
+
const raw = data[key];
|
|
1980
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
1981
|
+
const parsed = Date.parse(raw.replace(" ", "T"));
|
|
1982
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1983
|
+
};
|
|
1984
|
+
const endsAtMs = timeField("end_time");
|
|
1985
|
+
const startsAtMs = timeField("start_time");
|
|
1986
|
+
const claimedAtMs = timeField("claimed_at");
|
|
1987
|
+
return {
|
|
1988
|
+
kind,
|
|
1989
|
+
active: data["active"] === true,
|
|
1990
|
+
claimed: data["claimed"] === true,
|
|
1991
|
+
creditNum: numberField("credit_num"),
|
|
1992
|
+
...endsAtMs === void 0 ? {} : { endsAtMs },
|
|
1993
|
+
...startsAtMs === void 0 ? {} : { startsAtMs },
|
|
1994
|
+
...claimedAtMs === void 0 ? {} : { claimedAtMs }
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Claim one benefit campaign on ONE account (issue #79).
|
|
1999
|
+
*
|
|
2000
|
+
* This is a real credit grant on a real account, so it takes no account
|
|
2001
|
+
* argument other than the credential it is handed: the caller decides which
|
|
2002
|
+
* single account, and there is deliberately no batch form of this method. The
|
|
2003
|
+
* browser route is the only caller, and it guards the mutation with the same
|
|
2004
|
+
* POST + loopback checks the check-in route carries.
|
|
2005
|
+
*
|
|
2006
|
+
* The response is not parsed: the documented envelope of these two endpoints
|
|
2007
|
+
* is an empty success, and the caller re-reads the status afterwards. That
|
|
2008
|
+
* re-read, not this answer, is what the user is shown — which is why a claim
|
|
2009
|
+
* that silently did nothing still cannot leave the card saying 待领取.
|
|
2010
|
+
*/
|
|
2011
|
+
async claimBenefit(credential, kind) {
|
|
2012
|
+
const response = await fetch(`${billingBase(credential)}${BENEFIT_CLAIM_PATHS[kind]}`, {
|
|
2013
|
+
method: "POST",
|
|
2014
|
+
headers: billingHeaders(credential),
|
|
2015
|
+
body: "{}",
|
|
2016
|
+
signal: AbortSignal.timeout(JSON_TIMEOUT_MS)
|
|
2017
|
+
});
|
|
2018
|
+
const envelope = await readEnvelope(response);
|
|
2019
|
+
if (!response.ok || envelope.code !== 0) throw envelopeError(response.status, envelope);
|
|
2020
|
+
}
|
|
1913
2021
|
/** Claim today's check-in reward. The browser route guards this mutation. */
|
|
1914
2022
|
async claimDailyCheckin(credential) {
|
|
1915
2023
|
const response = await fetch(`${billingBase(credential)}/v2/billing/meter/daily-checkin`, {
|
|
@@ -3966,7 +4074,7 @@ function generationOf(version) {
|
|
|
3966
4074
|
//#endregion
|
|
3967
4075
|
//#region src/version.ts
|
|
3968
4076
|
/** The npm package version this build was produced from. */
|
|
3969
|
-
const WORKBUDDY_CONNECT_VERSION = "4.
|
|
4077
|
+
const WORKBUDDY_CONNECT_VERSION = "4.10.0";
|
|
3970
4078
|
//#endregion
|
|
3971
4079
|
//#region src/host-heartbeat.ts
|
|
3972
4080
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -706,6 +706,47 @@ interface WorkBuddyCheckinClaim {
|
|
|
706
706
|
streakDays: number;
|
|
707
707
|
isStreakDay: boolean;
|
|
708
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* One promotional benefit the account may or may not still be entitled to.
|
|
711
|
+
*
|
|
712
|
+
* Read from the billing gateway's OWN status endpoints — the same two the
|
|
713
|
+
* 「套餐与积分」 page calls before it decides whether to render a claim card
|
|
714
|
+
* (`/billing/meter/compensation-status`, `/billing/meter/check-gift-claimed`).
|
|
715
|
+
* Reading them is what makes the card honest: the plugin shows a benefit only
|
|
716
|
+
* while the gateway itself would still show one.
|
|
717
|
+
*
|
|
718
|
+
* The two flags answer DIFFERENT questions and neither can be derived from the
|
|
719
|
+
* other — this is the single most important fact about this shape, measured on
|
|
720
|
+
* a real account pool:
|
|
721
|
+
*
|
|
722
|
+
* - `active` is about the WINDOW, not about the reader. An account that has
|
|
723
|
+
* already claimed still reports `active: true` (observed: `claimed: true`
|
|
724
|
+
* together with `active: true` on a claimed gift), so `active` alone cannot
|
|
725
|
+
* mean "claimable".
|
|
726
|
+
* - `claimed` is about THIS account, and it survives the window: once the
|
|
727
|
+
* campaign lapses the endpoint keeps answering `claimed: false` with
|
|
728
|
+
* `active: false` (observed on every international account, whose campaign
|
|
729
|
+
* ended months ago).
|
|
730
|
+
*
|
|
731
|
+
* Hence a benefit is claimable exactly when {@link active} AND NOT
|
|
732
|
+
* {@link claimed}. See `benefitOf` in `src/benefits.ts`.
|
|
733
|
+
*/
|
|
734
|
+
interface WorkBuddyBenefit {
|
|
735
|
+
/** Which campaign this is; the plugin renders one row per kind. */
|
|
736
|
+
kind: 'compensation' | 'gift';
|
|
737
|
+
/** The campaign is inside its window (empty, see: already claimed ≠ inactive). */
|
|
738
|
+
active: boolean;
|
|
739
|
+
/** This account has already taken it — permanently, even after the window. */
|
|
740
|
+
claimed: boolean;
|
|
741
|
+
/** Credits on offer, as the gateway states them. */
|
|
742
|
+
creditNum: number;
|
|
743
|
+
/** Window end, epoch ms. Absent when the gateway sent none it could parse. */
|
|
744
|
+
endsAtMs?: number;
|
|
745
|
+
/** Window start, epoch ms. Compensation omits it. */
|
|
746
|
+
startsAtMs?: number;
|
|
747
|
+
/** When this account claimed it, epoch ms. Only meaningful with `claimed`. */
|
|
748
|
+
claimedAtMs?: number;
|
|
749
|
+
}
|
|
709
750
|
/** Token refresh answer; fields the upstream omits stay absent. */
|
|
710
751
|
interface WorkBuddyRefreshOutcome {
|
|
711
752
|
accessToken: string;
|
|
@@ -754,6 +795,23 @@ interface UpstreamErrorDetail {
|
|
|
754
795
|
requestId?: string;
|
|
755
796
|
/** Best human-facing message: `displayMsg.zh`, falling back to `.en`, then `msg`. */
|
|
756
797
|
displayMsg?: string;
|
|
798
|
+
/**
|
|
799
|
+
* What the upstream says to DO about it: `displayTips.zh`, falling back to
|
|
800
|
+
* `.en` (issue #78).
|
|
801
|
+
*
|
|
802
|
+
* Kept separate from {@link displayMsg} because the two answer different
|
|
803
|
+
* questions and both are needed: `displayMsg` names what was rejected
|
|
804
|
+
* ("模型无法处理此请求"), while `displayTips` names the REMEDY ("请新建任务、换个
|
|
805
|
+
* 模型或稍后重试"). The generic failure path used to discard it and render the
|
|
806
|
+
* raw body instead, whose first 400 characters are the `code`/`requestId`/
|
|
807
|
+
* `extError` skeleton — so the one actionable sentence was always cut off, and
|
|
808
|
+
* a plain parameter rejection read like an empty account pool.
|
|
809
|
+
*
|
|
810
|
+
* Recorded as data rather than folded into `displayMsg` so a caller that wants
|
|
811
|
+
* to tell the user what to change can do so without the "what went wrong"
|
|
812
|
+
* clause losing its own meaning.
|
|
813
|
+
*/
|
|
814
|
+
tips?: string;
|
|
757
815
|
}
|
|
758
816
|
/**
|
|
759
817
|
* Region for a login domain; an empty domain means CN (matching upstream tooling).
|
|
@@ -917,6 +975,41 @@ declare class WorkBuddyUpstreamClient {
|
|
|
917
975
|
fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
|
|
918
976
|
/** Query today's check-in status without changing account state. */
|
|
919
977
|
fetchCheckinStatus(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinStatus>;
|
|
978
|
+
/**
|
|
979
|
+
* Read every promotional benefit this account can still see, WITHOUT claiming
|
|
980
|
+
* anything.
|
|
981
|
+
*
|
|
982
|
+
* The campaign list is closed and small on purpose: the gateway exposes one
|
|
983
|
+
* status endpoint per campaign, so "discover benefits" is a fixed fan-out, not
|
|
984
|
+
* a search. A new campaign type means a new `/billing/meter/*-status` endpoint
|
|
985
|
+
* added here — the plugin never guesses at undocumented ones.
|
|
986
|
+
*
|
|
987
|
+
* Every endpoint is read in parallel because they are independent, and a
|
|
988
|
+
* failure is per-endpoint: one campaign answering 5xx must not blank out the
|
|
989
|
+
* other. The caller gets only what was actually read.
|
|
990
|
+
*
|
|
991
|
+
* Note the paths carry NO `/v2` prefix — these are not the `/v2/billing/meter`
|
|
992
|
+
* family the credit and check-in calls use. Probing `/v2/...` answers 404, which
|
|
993
|
+
* is how this distinction was found.
|
|
994
|
+
*/
|
|
995
|
+
fetchBenefits(credential: WorkBuddyCredential): Promise<readonly WorkBuddyBenefit[]>;
|
|
996
|
+
/** One benefit status endpoint. Throws on anything that is not a clean `code: 0`. */
|
|
997
|
+
private readBenefitStatus;
|
|
998
|
+
/**
|
|
999
|
+
* Claim one benefit campaign on ONE account (issue #79).
|
|
1000
|
+
*
|
|
1001
|
+
* This is a real credit grant on a real account, so it takes no account
|
|
1002
|
+
* argument other than the credential it is handed: the caller decides which
|
|
1003
|
+
* single account, and there is deliberately no batch form of this method. The
|
|
1004
|
+
* browser route is the only caller, and it guards the mutation with the same
|
|
1005
|
+
* POST + loopback checks the check-in route carries.
|
|
1006
|
+
*
|
|
1007
|
+
* The response is not parsed: the documented envelope of these two endpoints
|
|
1008
|
+
* is an empty success, and the caller re-reads the status afterwards. That
|
|
1009
|
+
* re-read, not this answer, is what the user is shown — which is why a claim
|
|
1010
|
+
* that silently did nothing still cannot leave the card saying 待领取.
|
|
1011
|
+
*/
|
|
1012
|
+
claimBenefit(credential: WorkBuddyCredential, kind: WorkBuddyBenefit['kind']): Promise<void>;
|
|
920
1013
|
/** Claim today's check-in reward. The browser route guards this mutation. */
|
|
921
1014
|
claimDailyCheckin(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinClaim>;
|
|
922
1015
|
/**
|
|
@@ -1994,6 +2087,29 @@ declare function probeModel(input: {
|
|
|
1994
2087
|
signal?: AbortSignal;
|
|
1995
2088
|
}): Promise<WorkBuddyProbeResult>;
|
|
1996
2089
|
//#endregion
|
|
2090
|
+
//#region src/benefits.d.ts
|
|
2091
|
+
/** What the account card shows for one campaign. */
|
|
2092
|
+
type WorkBuddyBenefitState =
|
|
2093
|
+
/** Offered, inside its window, not taken yet. The only state with a claim button. */
|
|
2094
|
+
'claimable' |
|
|
2095
|
+
/** This account already took it. Rendered as nothing at all. */
|
|
2096
|
+
'claimed' |
|
|
2097
|
+
/** The window is over and it was never taken — the offer is gone. */
|
|
2098
|
+
'expired';
|
|
2099
|
+
/** One campaign, resolved for display. */
|
|
2100
|
+
interface WorkBuddyBenefitView {
|
|
2101
|
+
kind: WorkBuddyBenefit['kind'];
|
|
2102
|
+
state: WorkBuddyBenefitState;
|
|
2103
|
+
/** Credits on offer, as the gateway stated them. */
|
|
2104
|
+
creditNum: number;
|
|
2105
|
+
/** Window end, epoch ms. */
|
|
2106
|
+
endsAtMs?: number;
|
|
2107
|
+
/** Whole days until the window closes, floored at 0. Absent without `endsAtMs`. */
|
|
2108
|
+
daysLeft?: number;
|
|
2109
|
+
/** True while the window closes within {@link URGENT_DAYS} days. */
|
|
2110
|
+
urgent: boolean;
|
|
2111
|
+
}
|
|
2112
|
+
//#endregion
|
|
1997
2113
|
//#region src/account-pool.d.ts
|
|
1998
2114
|
/**
|
|
1999
2115
|
* Which of the pool's writers produced a measurement.
|
|
@@ -2634,6 +2750,24 @@ type WorkBuddyWebPoolExclusion = 'unusable' | 'rate-limited' |
|
|
|
2634
2750
|
* does not say (issue #56).
|
|
2635
2751
|
*/
|
|
2636
2752
|
'model-busy' | 'out-of-credit' | 'credential-rejected' | 'not-found' | 'unavailable' | 'failed';
|
|
2753
|
+
/** One claimable benefit, as the card renders it (issue #79). */
|
|
2754
|
+
interface WorkBuddyWebBenefit {
|
|
2755
|
+
/** Which campaign: compensation (crash补偿) or gift (新人首月赠送). */
|
|
2756
|
+
kind: 'compensation' | 'gift';
|
|
2757
|
+
/** Display name for the campaign, resolved Host-side so both clients agree. */
|
|
2758
|
+
title: string;
|
|
2759
|
+
/** Credits on offer. */
|
|
2760
|
+
creditNum: number;
|
|
2761
|
+
/**
|
|
2762
|
+
* Whole days until the window closes, floored at 0.
|
|
2763
|
+
*
|
|
2764
|
+
* ABSENT when the gateway stated no end time — the card must then omit the
|
|
2765
|
+
* countdown rather than print a number nobody knows.
|
|
2766
|
+
*/
|
|
2767
|
+
daysLeft?: number;
|
|
2768
|
+
/** True when the window closes within 3 days, so the card can say 即将到期. */
|
|
2769
|
+
urgent: boolean;
|
|
2770
|
+
}
|
|
2637
2771
|
/**
|
|
2638
2772
|
* One account's row in the pool, as the card renders it.
|
|
2639
2773
|
*
|
|
@@ -2710,6 +2844,16 @@ interface WorkBuddyWebPoolAccount {
|
|
|
2710
2844
|
* result the user just watched succeed.
|
|
2711
2845
|
*/
|
|
2712
2846
|
checkedInToday?: boolean;
|
|
2847
|
+
/**
|
|
2848
|
+
* Promotional benefits this account can STILL claim (issue #79).
|
|
2849
|
+
*
|
|
2850
|
+
* Never carries a claimed or lapsed campaign: the issue's rule is that those
|
|
2851
|
+
* do not appear at all, so their absence here IS the "hide it" instruction.
|
|
2852
|
+
* ABSENT means not read (or nothing claimable), and the card renders no line
|
|
2853
|
+
* either way — a benefit line is only ever added to say "there is something
|
|
2854
|
+
* here for you", never to announce its own absence.
|
|
2855
|
+
*/
|
|
2856
|
+
benefits?: readonly WorkBuddyWebBenefit[];
|
|
2713
2857
|
/**
|
|
2714
2858
|
* The model a TEST of this row would use (issue #61).
|
|
2715
2859
|
*
|
|
@@ -2867,7 +3011,7 @@ declare const WORKBUDDY_SETTINGS_NS: "workbuddy";
|
|
|
2867
3011
|
interface WorkBuddyStatusRouteOptions {
|
|
2868
3012
|
/** The region-scoped credential store backing each region's requests. */
|
|
2869
3013
|
store(region: WorkBuddyRegion): WorkBuddyCredentialStore;
|
|
2870
|
-
client: Pick<WorkBuddyUpstreamClient, 'fetchCredits' | 'fetchCheckinStatus' | 'claimDailyCheckin'>;
|
|
3014
|
+
client: Pick<WorkBuddyUpstreamClient, 'fetchCredits' | 'fetchCheckinStatus' | 'claimDailyCheckin' | 'fetchBenefits' | 'claimBenefit'>;
|
|
2871
3015
|
/**
|
|
2872
3016
|
* The requested region's last-refreshed model directory (unfiltered) for
|
|
2873
3017
|
* card display. Region-scoped because the CN and international apps expose
|
|
@@ -3153,6 +3297,37 @@ interface WorkBuddyPoolDeps {
|
|
|
3153
3297
|
* per-account read in the full document.
|
|
3154
3298
|
*/
|
|
3155
3299
|
cachedCheckedInToday?(region: WorkBuddyRegion): Readonly<Record<string, boolean>>;
|
|
3300
|
+
/**
|
|
3301
|
+
* Claimable promotional benefits per account id (issue #79).
|
|
3302
|
+
*
|
|
3303
|
+
* Only campaigns the account can STILL claim are present; a claimed or lapsed
|
|
3304
|
+
* campaign is absent on purpose, so the card renders nothing for it. Absent
|
|
3305
|
+
* (or a missing entry) also means "not read yet", which the card treats the
|
|
3306
|
+
* same way — no line, rather than a guessed state.
|
|
3307
|
+
*/
|
|
3308
|
+
benefits?(region: WorkBuddyRegion): Promise<Readonly<Record<string, readonly WorkBuddyBenefitView[]>>>;
|
|
3309
|
+
/**
|
|
3310
|
+
* Claimable benefits, from the readings already taken this process.
|
|
3311
|
+
*
|
|
3312
|
+
* The first-paint counterpart of {@link benefits}: no request, so painting
|
|
3313
|
+
* never waits on two upstream reads per account.
|
|
3314
|
+
*/
|
|
3315
|
+
cachedBenefits?(region: WorkBuddyRegion): Readonly<Record<string, readonly WorkBuddyBenefitView[]>>;
|
|
3316
|
+
/**
|
|
3317
|
+
* Forget this account's cached benefit reading so the next status read is live.
|
|
3318
|
+
*
|
|
3319
|
+
* The claim route writes to upstream directly, so nothing else in the plugin
|
|
3320
|
+
* knows the campaign just changed. Without this the card kept showing 领取 for
|
|
3321
|
+
* up to `BENEFIT_CACHE_MS` (30 min) after a SUCCESSFUL claim: the credits went
|
|
3322
|
+
* up, the pill stayed, and the only remaining explanation was "the claim
|
|
3323
|
+
* silently failed" — which invited a second click the route then refused as
|
|
3324
|
+
* already-claimed. `checkin` has the same rule and invalidates its own cache;
|
|
3325
|
+
* this is the benefit counterpart.
|
|
3326
|
+
*
|
|
3327
|
+
* Absent is legal (a Host from before this hook) and means "the click refreshes
|
|
3328
|
+
* the card, but the host-side cache may still answer stale for a while".
|
|
3329
|
+
*/
|
|
3330
|
+
invalidateBenefit?(region: WorkBuddyRegion, accountId: string): void;
|
|
3156
3331
|
/**
|
|
3157
3332
|
* Every local sign-in the pool does NOT cover, so the card can offer them to
|
|
3158
3333
|
* check in. Absent means the card lists only pool members.
|