@wefunder/sdk 0.1.0-beta.10 → 0.1.0-beta.11
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/dist/index.cjs +159 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +157 -28
- package/dist/index.js.map +1 -1
- package/examples_manifest.json +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,10 +33,12 @@ __export(index_exports, {
|
|
|
33
33
|
REQUEST_ID_HEADER: () => REQUEST_ID_HEADER,
|
|
34
34
|
SANDBOX_AUTHORIZE_BASE_URL: () => SANDBOX_AUTHORIZE_BASE_URL,
|
|
35
35
|
SIGNATURE_HEADER: () => SIGNATURE_HEADER,
|
|
36
|
+
TokenManager: () => TokenManager,
|
|
36
37
|
WebhookSignatureError: () => WebhookSignatureError,
|
|
37
38
|
Wefunder: () => Wefunder,
|
|
38
39
|
WefunderAuthError: () => WefunderAuthError,
|
|
39
40
|
WefunderError: () => WefunderError,
|
|
41
|
+
WefunderTokenPersistenceError: () => WefunderTokenPersistenceError,
|
|
40
42
|
checkWebhookSignature: () => checkWebhookSignature,
|
|
41
43
|
clientCredentialsGrant: () => clientCredentialsGrant,
|
|
42
44
|
collect: () => collect,
|
|
@@ -1074,6 +1076,20 @@ var WefunderAuthError = class extends WefunderError {
|
|
|
1074
1076
|
};
|
|
1075
1077
|
|
|
1076
1078
|
// src/token-manager.ts
|
|
1079
|
+
var WefunderTokenPersistenceError = class extends Error {
|
|
1080
|
+
tokens;
|
|
1081
|
+
constructor(tokens, cause) {
|
|
1082
|
+
super(
|
|
1083
|
+
`Token store failed to save the rotated token set: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
1084
|
+
{ cause }
|
|
1085
|
+
);
|
|
1086
|
+
this.name = "WefunderTokenPersistenceError";
|
|
1087
|
+
this.tokens = tokens;
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
function sameTokenSet(a, b) {
|
|
1091
|
+
return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken;
|
|
1092
|
+
}
|
|
1077
1093
|
var TokenManager = class {
|
|
1078
1094
|
#tokens;
|
|
1079
1095
|
#clientId;
|
|
@@ -1086,6 +1102,7 @@ var TokenManager = class {
|
|
|
1086
1102
|
#now;
|
|
1087
1103
|
#leeway;
|
|
1088
1104
|
#inflight;
|
|
1105
|
+
#pending;
|
|
1089
1106
|
constructor(opts) {
|
|
1090
1107
|
this.#tokens = opts.tokens;
|
|
1091
1108
|
this.#clientId = opts.clientId;
|
|
@@ -1098,15 +1115,40 @@ var TokenManager = class {
|
|
|
1098
1115
|
this.#now = opts.now ?? Date.now;
|
|
1099
1116
|
this.#leeway = opts.expiryLeewayMs ?? 3e4;
|
|
1100
1117
|
}
|
|
1118
|
+
/** The durable, in-use token set. A rotated set that could not be persisted sits in `pendingTokens`. */
|
|
1101
1119
|
get current() {
|
|
1102
1120
|
return this.#tokens;
|
|
1103
1121
|
}
|
|
1104
1122
|
/** True if the manager can recover an expired token (rotate a refresh token or re-mint). */
|
|
1105
1123
|
get canRefresh() {
|
|
1106
|
-
return Boolean(
|
|
1124
|
+
return Boolean(
|
|
1125
|
+
this.#tokens.refreshToken && this.#clientId || this.#reMint
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
/** A rotated set awaiting a successful `store.save` (see `WefunderTokenPersistenceError`). */
|
|
1129
|
+
get pendingTokens() {
|
|
1130
|
+
return this.#pending;
|
|
1107
1131
|
}
|
|
1108
|
-
/**
|
|
1132
|
+
/**
|
|
1133
|
+
* Tell the manager you persisted `tokens` (the set from a `WefunderTokenPersistenceError`)
|
|
1134
|
+
* yourself. Publishes it only if it is still the pending set — a stale acknowledgment (the
|
|
1135
|
+
* manager has since rotated again) is a no-op and returns false, so an older save can never
|
|
1136
|
+
* publish a newer, unsaved set.
|
|
1137
|
+
*/
|
|
1138
|
+
async markPersisted(tokens) {
|
|
1139
|
+
const pending = this.#pending;
|
|
1140
|
+
if (!pending || !sameTokenSet(pending, tokens)) return false;
|
|
1141
|
+
this.#pending = void 0;
|
|
1142
|
+
this.#tokens = pending;
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Returns a valid access token, refreshing proactively if it's expired/near-expiry. If a
|
|
1147
|
+
* rotated set is pending persistence, the save is retried first — no request uses an
|
|
1148
|
+
* undurable token.
|
|
1149
|
+
*/
|
|
1109
1150
|
async getAccessToken() {
|
|
1151
|
+
if (this.#pending) await this.refresh();
|
|
1110
1152
|
const { expiresAt } = this.#tokens;
|
|
1111
1153
|
if (expiresAt !== void 0 && this.#now() >= expiresAt - this.#leeway && this.canRefresh) {
|
|
1112
1154
|
await this.refresh();
|
|
@@ -1120,6 +1162,14 @@ var TokenManager = class {
|
|
|
1120
1162
|
*/
|
|
1121
1163
|
async refresh() {
|
|
1122
1164
|
if (this.#inflight) return this.#inflight;
|
|
1165
|
+
if (this.#pending) {
|
|
1166
|
+
this.#inflight = this.#publishPending();
|
|
1167
|
+
try {
|
|
1168
|
+
return await this.#inflight;
|
|
1169
|
+
} finally {
|
|
1170
|
+
this.#inflight = void 0;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1123
1173
|
const strategy = this.#recoveryStrategy();
|
|
1124
1174
|
if (!strategy) {
|
|
1125
1175
|
throw new WefunderAuthError(
|
|
@@ -1127,11 +1177,8 @@ var TokenManager = class {
|
|
|
1127
1177
|
);
|
|
1128
1178
|
}
|
|
1129
1179
|
this.#inflight = (async () => {
|
|
1130
|
-
|
|
1131
|
-
this.#
|
|
1132
|
-
await this.#store?.save(next);
|
|
1133
|
-
await this.#onTokenRefresh?.(next);
|
|
1134
|
-
return next;
|
|
1180
|
+
this.#pending = await strategy();
|
|
1181
|
+
return this.#publishPending();
|
|
1135
1182
|
})();
|
|
1136
1183
|
try {
|
|
1137
1184
|
return await this.#inflight;
|
|
@@ -1139,6 +1186,22 @@ var TokenManager = class {
|
|
|
1139
1186
|
this.#inflight = void 0;
|
|
1140
1187
|
}
|
|
1141
1188
|
}
|
|
1189
|
+
// Persist BEFORE publishing: no caller may use the rotated token until it is durable, and a
|
|
1190
|
+
// failed save must not leave the process working in memory but unable to reconnect after a
|
|
1191
|
+
// restart. On failure the set stays pending and WefunderTokenPersistenceError is thrown; the
|
|
1192
|
+
// next call retries the save.
|
|
1193
|
+
async #publishPending() {
|
|
1194
|
+
const tokens = this.#pending;
|
|
1195
|
+
try {
|
|
1196
|
+
await this.#store?.save(tokens);
|
|
1197
|
+
await this.#onTokenRefresh?.(tokens);
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
throw new WefunderTokenPersistenceError(tokens, err);
|
|
1200
|
+
}
|
|
1201
|
+
this.#pending = void 0;
|
|
1202
|
+
this.#tokens = tokens;
|
|
1203
|
+
return tokens;
|
|
1204
|
+
}
|
|
1142
1205
|
// Pick the recovery strategy: refresh_token rotation, else cc re-mint, else none.
|
|
1143
1206
|
#recoveryStrategy() {
|
|
1144
1207
|
const refreshTokenValue = this.#tokens.refreshToken;
|
|
@@ -1831,12 +1894,22 @@ var Wefunder = class _Wefunder {
|
|
|
1831
1894
|
});
|
|
1832
1895
|
}
|
|
1833
1896
|
/** The live token set (e.g. to persist after construction). */
|
|
1897
|
+
/** A rotated set awaiting a successful save (see `WefunderTokenPersistenceError`). */
|
|
1898
|
+
get pendingTokens() {
|
|
1899
|
+
return this.#tokens.pendingTokens;
|
|
1900
|
+
}
|
|
1901
|
+
/** Acknowledge an out-of-band save of `tokens`; false if that set is no longer pending. */
|
|
1902
|
+
markPersisted(tokens) {
|
|
1903
|
+
return this.#tokens.markPersisted(tokens);
|
|
1904
|
+
}
|
|
1834
1905
|
get tokens() {
|
|
1835
1906
|
return this.#tokens.current;
|
|
1836
1907
|
}
|
|
1837
1908
|
// ---- unwrap: turn the {data,error,response} result into data-or-throw ----
|
|
1838
1909
|
async #unwrap(p) {
|
|
1839
1910
|
const { data, error, response } = await p;
|
|
1911
|
+
if (error instanceof WefunderTokenPersistenceError || error instanceof WefunderError)
|
|
1912
|
+
throw error;
|
|
1840
1913
|
if (response && response.ok && error === void 0) return data;
|
|
1841
1914
|
const status = response?.status ?? 0;
|
|
1842
1915
|
const env = error ?? {};
|
|
@@ -1845,7 +1918,10 @@ var Wefunder = class _Wefunder {
|
|
|
1845
1918
|
type: env.error?.type ?? "api_error",
|
|
1846
1919
|
message: env.error?.message ?? response?.statusText ?? "Request failed",
|
|
1847
1920
|
// X-Wf-Request-Id header is primary (present even on non-JSON edge errors).
|
|
1848
|
-
requestId: requestIdFrom(
|
|
1921
|
+
requestId: requestIdFrom(
|
|
1922
|
+
response,
|
|
1923
|
+
env.error?.request_id ?? env.request_id
|
|
1924
|
+
),
|
|
1849
1925
|
details: env.error?.details,
|
|
1850
1926
|
remediation: env.error?.remediation
|
|
1851
1927
|
});
|
|
@@ -1881,11 +1957,16 @@ var Wefunder = class _Wefunder {
|
|
|
1881
1957
|
me: () => this.#unwrapData(getCurrentUser({ client: this.#client }))
|
|
1882
1958
|
};
|
|
1883
1959
|
offerings = {
|
|
1884
|
-
list: this.#page(
|
|
1960
|
+
list: this.#page(
|
|
1961
|
+
listOfferings
|
|
1962
|
+
),
|
|
1885
1963
|
all: (query) => paginate((cursor) => this.offerings.list({ ...query, cursor })),
|
|
1886
1964
|
collect: (query) => collect((cursor) => this.offerings.list({ ...query, cursor })),
|
|
1887
1965
|
get: (externalId) => this.#unwrapData(
|
|
1888
|
-
getOffering({
|
|
1966
|
+
getOffering({
|
|
1967
|
+
client: this.#client,
|
|
1968
|
+
path: { external_id: externalId }
|
|
1969
|
+
})
|
|
1889
1970
|
)
|
|
1890
1971
|
};
|
|
1891
1972
|
/**
|
|
@@ -1894,20 +1975,40 @@ var Wefunder = class _Wefunder {
|
|
|
1894
1975
|
* from your last page — it is always present — and resume from it next time.
|
|
1895
1976
|
*/
|
|
1896
1977
|
investments = {
|
|
1897
|
-
list: this.#page(
|
|
1898
|
-
|
|
1899
|
-
|
|
1978
|
+
list: this.#page(
|
|
1979
|
+
listInvestments
|
|
1980
|
+
),
|
|
1981
|
+
all: (query) => paginate(
|
|
1982
|
+
(cursor) => this.investments.list({
|
|
1983
|
+
...query,
|
|
1984
|
+
cursor
|
|
1985
|
+
})
|
|
1986
|
+
),
|
|
1987
|
+
collect: (query) => collect(
|
|
1988
|
+
(cursor) => this.investments.list({
|
|
1989
|
+
...query,
|
|
1990
|
+
cursor
|
|
1991
|
+
})
|
|
1992
|
+
),
|
|
1900
1993
|
/** The current record (not the published projection) for one investment (`inv_…`). */
|
|
1901
|
-
get: (id) => this.#unwrapData(
|
|
1994
|
+
get: (id) => this.#unwrapData(
|
|
1995
|
+
getInvestment({ client: this.#client, path: { id } })
|
|
1996
|
+
)
|
|
1902
1997
|
};
|
|
1903
1998
|
portfolio = {
|
|
1904
|
-
get: (query) => this.#unwrapData(
|
|
1999
|
+
get: (query) => this.#unwrapData(
|
|
2000
|
+
getPortfolio({ client: this.#client, query })
|
|
2001
|
+
),
|
|
1905
2002
|
positions: {
|
|
1906
2003
|
list: this.#page(
|
|
1907
2004
|
listPortfolioPositions
|
|
1908
2005
|
),
|
|
1909
|
-
all: (query) => paginate(
|
|
1910
|
-
|
|
2006
|
+
all: (query) => paginate(
|
|
2007
|
+
(cursor) => this.portfolio.positions.list({ ...query, cursor })
|
|
2008
|
+
),
|
|
2009
|
+
collect: (query) => collect(
|
|
2010
|
+
(cursor) => this.portfolio.positions.list({ ...query, cursor })
|
|
2011
|
+
)
|
|
1911
2012
|
}
|
|
1912
2013
|
};
|
|
1913
2014
|
campaigns = {
|
|
@@ -1916,17 +2017,25 @@ var Wefunder = class _Wefunder {
|
|
|
1916
2017
|
collect: () => collect((cursor) => this.campaigns.list({ cursor }))
|
|
1917
2018
|
};
|
|
1918
2019
|
syndicates = {
|
|
1919
|
-
list: this.#page(
|
|
2020
|
+
list: this.#page(
|
|
2021
|
+
listSyndicates
|
|
2022
|
+
),
|
|
1920
2023
|
all: (query) => paginate((cursor) => this.syndicates.list({ ...query, cursor })),
|
|
1921
|
-
get: (id) => this.#unwrapData(
|
|
2024
|
+
get: (id) => this.#unwrapData(
|
|
2025
|
+
getSyndicate({ client: this.#client, path: { id } })
|
|
2026
|
+
)
|
|
1922
2027
|
};
|
|
1923
2028
|
intents = {
|
|
1924
2029
|
list: this.#page(listIntents),
|
|
1925
2030
|
all: (query) => paginate((cursor) => this.intents.list({ ...query, cursor })),
|
|
1926
|
-
get: (id) => this.#unwrapData(
|
|
2031
|
+
get: (id) => this.#unwrapData(
|
|
2032
|
+
getIntent({ client: this.#client, path: { id } })
|
|
2033
|
+
)
|
|
1927
2034
|
};
|
|
1928
2035
|
attribution = {
|
|
1929
|
-
me: () => this.#unwrapData(
|
|
2036
|
+
me: () => this.#unwrapData(
|
|
2037
|
+
getAttributionMe({ client: this.#client })
|
|
2038
|
+
)
|
|
1930
2039
|
};
|
|
1931
2040
|
/**
|
|
1932
2041
|
* Webhook endpoints (`/webhook_endpoints`, scopes `read:webhooks` / `write:webhooks`).
|
|
@@ -1937,30 +2046,50 @@ var Wefunder = class _Wefunder {
|
|
|
1937
2046
|
*/
|
|
1938
2047
|
webhookEndpoints = {
|
|
1939
2048
|
/** All of your app's endpoints (secret omitted). `meta.quota` is the per-app limit. */
|
|
1940
|
-
list: () => this.#unwrap(
|
|
2049
|
+
list: () => this.#unwrap(
|
|
2050
|
+
listWebhookEndpoints({ client: this.#client })
|
|
2051
|
+
),
|
|
1941
2052
|
get: (id) => this.#unwrapData(
|
|
1942
|
-
getWebhookEndpoint({
|
|
2053
|
+
getWebhookEndpoint({
|
|
2054
|
+
client: this.#client,
|
|
2055
|
+
path: { external_id: id }
|
|
2056
|
+
})
|
|
1943
2057
|
),
|
|
1944
2058
|
/** Store `attributes.secret` from the result — it is never shown again. */
|
|
1945
|
-
create: (input) => this.#unwrapData(
|
|
2059
|
+
create: (input) => this.#unwrapData(
|
|
2060
|
+
createWebhookEndpoint({ client: this.#client, body: input })
|
|
2061
|
+
),
|
|
1946
2062
|
/** `events` replaces the subscription list wholesale (no merge); omit to leave unchanged. */
|
|
1947
2063
|
update: (id, input) => this.#unwrapData(
|
|
1948
|
-
updateWebhookEndpoint({
|
|
2064
|
+
updateWebhookEndpoint({
|
|
2065
|
+
client: this.#client,
|
|
2066
|
+
path: { external_id: id },
|
|
2067
|
+
body: input
|
|
2068
|
+
})
|
|
1949
2069
|
),
|
|
1950
2070
|
/** Stops deliveries immediately. Removal is permanent — create a new endpoint to resume. */
|
|
1951
2071
|
remove: (id) => this.#unwrapData(
|
|
1952
|
-
deleteWebhookEndpoint({
|
|
2072
|
+
deleteWebhookEndpoint({
|
|
2073
|
+
client: this.#client,
|
|
2074
|
+
path: { external_id: id }
|
|
2075
|
+
})
|
|
1953
2076
|
),
|
|
1954
2077
|
/**
|
|
1955
2078
|
* New secret (shown once). The old one keeps signing for a 24h overlap — deliveries
|
|
1956
2079
|
* carry a `v1` for both, and `constructEvent` accepts either.
|
|
1957
2080
|
*/
|
|
1958
2081
|
rotateSecret: (id) => this.#unwrapData(
|
|
1959
|
-
rotateWebhookEndpointSecret({
|
|
2082
|
+
rotateWebhookEndpointSecret({
|
|
2083
|
+
client: this.#client,
|
|
2084
|
+
path: { external_id: id }
|
|
2085
|
+
})
|
|
1960
2086
|
),
|
|
1961
2087
|
/** Recover an auto-disabled endpoint after fixing your server. Idempotent. */
|
|
1962
2088
|
reenable: (id) => this.#unwrapData(
|
|
1963
|
-
reenableWebhookEndpoint({
|
|
2089
|
+
reenableWebhookEndpoint({
|
|
2090
|
+
client: this.#client,
|
|
2091
|
+
path: { external_id: id }
|
|
2092
|
+
})
|
|
1964
2093
|
),
|
|
1965
2094
|
/**
|
|
1966
2095
|
* POST a real, signed example event at the endpoint (production envelope + signature)
|
|
@@ -2156,10 +2285,12 @@ async function dispatchWebhook(event, handlers) {
|
|
|
2156
2285
|
REQUEST_ID_HEADER,
|
|
2157
2286
|
SANDBOX_AUTHORIZE_BASE_URL,
|
|
2158
2287
|
SIGNATURE_HEADER,
|
|
2288
|
+
TokenManager,
|
|
2159
2289
|
WebhookSignatureError,
|
|
2160
2290
|
Wefunder,
|
|
2161
2291
|
WefunderAuthError,
|
|
2162
2292
|
WefunderError,
|
|
2293
|
+
WefunderTokenPersistenceError,
|
|
2163
2294
|
checkWebhookSignature,
|
|
2164
2295
|
clientCredentialsGrant,
|
|
2165
2296
|
collect,
|