@salesforce/core 8.11.4 → 8.12.1-dev.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.
@@ -247,7 +247,7 @@ class ConfigFile extends configStore_1.BaseConfigStore {
247
247
  const lockResponse = await (0, fileLocking_1.lockInit)(this.getPath());
248
248
  // lock the file. Returns an unlock function to call when done.
249
249
  try {
250
- const fileTimestamp = (await fs.promises.stat(this.getPath(), { bigint: true })).mtimeNs;
250
+ const fileTimestamp = await getNsTimeStamp(this.getPath());
251
251
  const fileContents = (0, kit_1.parseJsonMap)(await fs.promises.readFile(this.getPath(), 'utf8'), this.getPath());
252
252
  this.logAndMergeContents(fileTimestamp, fileContents);
253
253
  }
@@ -268,7 +268,7 @@ class ConfigFile extends configStore_1.BaseConfigStore {
268
268
  const lockResponse = (0, fileLocking_1.lockInitSync)(this.getPath());
269
269
  try {
270
270
  // get the file modstamp. Do this after the lock acquisition in case the file is being written to.
271
- const fileTimestamp = fs.statSync(this.getPath(), { bigint: true }).mtimeNs;
271
+ const fileTimestamp = getNsTimeStampSync(this.getPath());
272
272
  const fileContents = (0, kit_1.parseJsonMap)(fs.readFileSync(this.getPath(), 'utf8'), this.getPath());
273
273
  this.logAndMergeContents(fileTimestamp, fileContents);
274
274
  }
@@ -394,4 +394,8 @@ class ConfigFile extends configStore_1.BaseConfigStore {
394
394
  }
395
395
  }
396
396
  exports.ConfigFile = ConfigFile;
397
+ const getNsTimeStamp = async (filePath) => getNsTimeStampFromStatus(await fs.promises.stat(filePath, { bigint: true }));
398
+ const getNsTimeStampSync = (filePath) => getNsTimeStampFromStatus(fs.statSync(filePath, { bigint: true }));
399
+ /** in browser environment, memfs is missing the bigInt ns timestamp, so we generate it from the ms */
400
+ const getNsTimeStampFromStatus = (stats) => stats.mtimeNs ?? BigInt(stats.mtimeMs) * BigInt(1_000_000);
397
401
  //# sourceMappingURL=configFile.js.map
@@ -58,6 +58,10 @@ const retrieveKeychain = async (platform) => {
58
58
  }
59
59
  }
60
60
  }
61
+ else if (platform === 'browser') {
62
+ logger.debug(`platform: ${platform}. Using generic keychain.`);
63
+ return keyChainImpl_1.keyChainImpl.generic_unix;
64
+ }
61
65
  else {
62
66
  throw messages.createError('unsupportedOperatingSystemError', [platform]);
63
67
  }
package/lib/global.d.ts CHANGED
@@ -32,6 +32,10 @@ export declare class Global {
32
32
  * The preferred global folder in which state is stored.
33
33
  */
34
34
  static readonly STATE_FOLDER = ".sfdx";
35
+ /**
36
+ * Whether the code is running in a web browser.
37
+ */
38
+ static get isWeb(): boolean;
35
39
  /**
36
40
  * The full system path to the global sfdx state folder.
37
41
  *
package/lib/global.js CHANGED
@@ -70,6 +70,12 @@ class Global {
70
70
  * The preferred global folder in which state is stored.
71
71
  */
72
72
  static STATE_FOLDER = Global.SFDX_STATE_FOLDER;
73
+ /**
74
+ * Whether the code is running in a web browser.
75
+ */
76
+ static get isWeb() {
77
+ return 'window' in globalThis;
78
+ }
73
79
  /**
74
80
  * The full system path to the global sfdx state folder.
75
81
  *
@@ -233,7 +233,7 @@ class Logger {
233
233
  * Gets the name of this logger.
234
234
  */
235
235
  getName() {
236
- return this.pinoLogger.bindings().name ?? '';
236
+ return (this.pinoLogger?.bindings ? this.pinoLogger.bindings().name : '') ?? '';
237
237
  }
238
238
  /**
239
239
  * Gets the current level of this logger.
@@ -409,7 +409,7 @@ const getWriteStream = (level = 'warn') => {
409
409
  // write to a rotating file
410
410
  target: 'pino/file',
411
411
  options: {
412
- destination: path.join(global_1.Global.SF_DIR, `sf-${rotator.get(logRotationPeriod) ?? rotator.get('1d')}.log`),
412
+ destination: path.join(global_1.Global.SF_DIR, `sf-${(0, ts_types_1.ensureString)(rotator.get(logRotationPeriod)) ?? rotator.get('1d')}.log`),
413
413
  mkdir: true,
414
414
  level,
415
415
  },
@@ -7,6 +7,15 @@ import { Org } from './org';
7
7
  * Fields for authorization, org, and local information.
8
8
  */
9
9
  export type AuthFields = {
10
+ apps?: {
11
+ [key: string]: {
12
+ clientId: string;
13
+ clientSecret?: string;
14
+ accessToken: string;
15
+ refreshToken: string;
16
+ oauthFlow: 'web';
17
+ };
18
+ };
10
19
  accessToken?: string;
11
20
  alias?: string;
12
21
  authCode?: string;
@@ -234,8 +243,10 @@ export declare class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
234
243
  update(authData?: AuthFields): AuthInfo;
235
244
  /**
236
245
  * Get the auth fields (decrypted) needed to make a connection.
246
+ *
247
+ * @param app Name of the CA/ECA associated with the user.
237
248
  */
238
- getConnectionOptions(): ConnectionOptions;
249
+ getConnectionOptions(app?: string): ConnectionOptions;
239
250
  getClientId(): string;
240
251
  getRedirectUri(): string;
241
252
  /**
@@ -343,6 +354,13 @@ export declare namespace AuthInfo {
343
354
  * OAuth options.
344
355
  */
345
356
  oauth2Options?: JwtOAuth2Config;
357
+ apps?: Array<{
358
+ name: string;
359
+ accessToken: string;
360
+ refreshToken: string;
361
+ clientId: string;
362
+ clientSecret?: string;
363
+ }>;
346
364
  /**
347
365
  * Options for the access token auth.
348
366
  */
@@ -445,38 +445,85 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
445
445
  }
446
446
  /**
447
447
  * Get the auth fields (decrypted) needed to make a connection.
448
+ *
449
+ * @param app Name of the CA/ECA associated with the user.
448
450
  */
449
- getConnectionOptions() {
451
+ getConnectionOptions(app) {
450
452
  const decryptedCopy = this.getFields(true);
451
453
  const { accessToken, instanceUrl, loginUrl } = decryptedCopy;
452
- if (this.isAccessTokenFlow()) {
453
- this.logger.info('Returning fields for a connection using access token.');
454
- // Just auth with the accessToken
455
- return { accessToken, instanceUrl, loginUrl };
456
- }
457
- if (this.isJwt()) {
458
- this.logger.info('Returning fields for a connection using JWT config.');
454
+ // return main app auth fields
455
+ if (!app) {
456
+ if (this.isAccessTokenFlow()) {
457
+ this.logger.info('Returning fields for a connection using access token.');
458
+ // Just auth with the accessToken
459
+ return { accessToken, instanceUrl, loginUrl };
460
+ }
461
+ if (this.isJwt()) {
462
+ this.logger.info('Returning fields for a connection using JWT config.');
463
+ return {
464
+ accessToken,
465
+ instanceUrl,
466
+ refreshFn: this.refreshFn.bind(this),
467
+ };
468
+ }
469
+ // @TODO: figure out loginUrl and redirectUri (probably get from config class)
470
+ //
471
+ // redirectUri: org.config.getOauthCallbackUrl()
472
+ // loginUrl: this.fields.instanceUrl || this.config.getAppConfig().sfdcLoginUrl
473
+ this.logger.info('Returning fields for a connection using OAuth config.');
474
+ // Decrypt a user provided client secret or use the default.
459
475
  return {
476
+ oauth2: {
477
+ loginUrl: instanceUrl ?? sfdcUrl_1.SfdcUrl.PRODUCTION,
478
+ clientId: this.getClientId(),
479
+ redirectUri: this.getRedirectUri(),
480
+ },
460
481
  accessToken,
461
482
  instanceUrl,
462
483
  refreshFn: this.refreshFn.bind(this),
463
484
  };
464
485
  }
465
- // @TODO: figure out loginUrl and redirectUri (probably get from config class)
466
- //
467
- // redirectUri: org.config.getOauthCallbackUrl()
468
- // loginUrl: this.fields.instanceUrl || this.config.getAppConfig().sfdcLoginUrl
469
- this.logger.info('Returning fields for a connection using OAuth config.');
470
- // Decrypt a user provided client secret or use the default.
486
+ if (!decryptedCopy.apps) {
487
+ throw new sfError_1.SfError(`${this.username} does not have any apps linked yet.`);
488
+ }
489
+ if (!(app in decryptedCopy.apps)) {
490
+ throw new sfError_1.SfError(`${this.username} does not have a "${app}" app linked yet.`);
491
+ }
492
+ const decryptedApp = decryptedCopy.apps[app];
471
493
  return {
472
494
  oauth2: {
473
495
  loginUrl: instanceUrl ?? sfdcUrl_1.SfdcUrl.PRODUCTION,
474
- clientId: this.getClientId(),
496
+ clientId: decryptedApp.clientId,
475
497
  redirectUri: this.getRedirectUri(),
476
498
  },
477
- accessToken,
499
+ accessToken: decryptedApp.accessToken,
478
500
  instanceUrl,
479
- refreshFn: this.refreshFn.bind(this),
501
+ // Specific refreshFn for AuthInfo's apps.
502
+ //
503
+ // Each app stores the oauth flow used for its initial auth, here we ensure each refresh returns
504
+ // a token, update the auth file with it and send it back to jsforce's through the callback.
505
+ refreshFn: async (_conn, callback) => {
506
+ // This only handles refresh for web flow.
507
+ // When more flows are supported for apps, check the `app.oauthFlow` field to set the appropiate refresh helper.
508
+ const authFields = await this.buildRefreshTokenConfig({
509
+ clientId: decryptedApp.clientId,
510
+ clientSecret: decryptedApp.clientSecret,
511
+ refreshToken: decryptedApp.refreshToken,
512
+ loginUrl: instanceUrl,
513
+ });
514
+ await this.save({
515
+ apps: {
516
+ [app]: {
517
+ accessToken: (0, ts_types_1.ensureString)(authFields.accessToken),
518
+ clientId: decryptedApp.clientId,
519
+ clientSecret: decryptedApp.clientSecret,
520
+ refreshToken: decryptedApp.refreshToken,
521
+ oauthFlow: 'web',
522
+ },
523
+ },
524
+ });
525
+ await callback(null, authFields.accessToken);
526
+ },
480
527
  };
481
528
  }
482
529
  getClientId() {
@@ -184,5 +184,9 @@ export declare namespace Connection {
184
184
  * Additional connection parameters.
185
185
  */
186
186
  connectionOptions?: ConnectionConfig<S>;
187
+ /**
188
+ * App to use for auth info credentials.
189
+ */
190
+ app?: string;
187
191
  };
188
192
  }
@@ -86,7 +86,7 @@ class Connection extends jsforce_node_1.Connection {
86
86
  callOptions: {
87
87
  client: clientId,
88
88
  },
89
- ...options.authInfo.getConnectionOptions(),
89
+ ...options.authInfo.getConnectionOptions(options.app),
90
90
  // this assertion is questionable, but has existed before core7
91
91
  };
92
92
  const conn = new this({ ...options, connectionOptions });
@@ -88,6 +88,5 @@ export declare class AliasAccessor extends AsyncOptionalCreatable {
88
88
  * if the file doesn't exist, create it empty
89
89
  */
90
90
  private readFileToAliasStore;
91
- private saveAliasStoreToFile;
92
91
  }
93
92
  export declare const getFileLocation: () => string;
@@ -10,11 +10,10 @@ exports.getFileLocation = exports.AliasAccessor = exports.FILENAME = exports.DEF
10
10
  const node_path_1 = require("node:path");
11
11
  const node_os_1 = require("node:os");
12
12
  const promises_1 = require("node:fs/promises");
13
- const proper_lockfile_1 = require("proper-lockfile");
14
13
  const kit_1 = require("@salesforce/kit");
15
14
  const global_1 = require("../../global");
16
15
  const sfError_1 = require("../../sfError");
17
- const lockRetryOptions_1 = require("../../util/lockRetryOptions");
16
+ const fileLocking_1 = require("../../util/fileLocking");
18
17
  exports.DEFAULT_GROUP = 'orgs';
19
18
  exports.FILENAME = 'alias.json';
20
19
  class AliasAccessor extends kit_1.AsyncOptionalCreatable {
@@ -94,18 +93,20 @@ class AliasAccessor extends kit_1.AsyncOptionalCreatable {
94
93
  */
95
94
  async setAndSave(alias, entity) {
96
95
  // get a very fresh copy to merge with to avoid conflicts, then lock
97
- await this.readFileToAliasStore(true);
96
+ const lockResponse = await (0, fileLocking_1.lockInit)(this.fileLocation);
97
+ await this.readFileToAliasStore();
98
98
  this.aliasStore.set(alias, getNameOf(entity));
99
- return this.saveAliasStoreToFile();
99
+ return lockResponse.writeAndUnlock(aliasStoreToRawFileContents(this.aliasStore));
100
100
  }
101
101
  /**
102
102
  * Unset the given alias(es). Writes to the file
103
103
  *
104
104
  */
105
105
  async unsetAndSave(alias) {
106
- await this.readFileToAliasStore(true);
106
+ const lockResponse = await (0, fileLocking_1.lockInit)(this.fileLocation);
107
+ await this.readFileToAliasStore();
107
108
  this.aliasStore.delete(alias);
108
- return this.saveAliasStoreToFile();
109
+ return lockResponse.writeAndUnlock(aliasStoreToRawFileContents(this.aliasStore));
109
110
  }
110
111
  /**
111
112
  * Unset all the aliases for the given array of entity.
@@ -113,11 +114,12 @@ class AliasAccessor extends kit_1.AsyncOptionalCreatable {
113
114
  * @param entity the aliasable entity for which you want to unset all aliases
114
115
  */
115
116
  async unsetValuesAndSave(aliasees) {
116
- await this.readFileToAliasStore(true);
117
+ const lockResponse = await (0, fileLocking_1.lockInit)(this.fileLocation);
118
+ await this.readFileToAliasStore();
117
119
  (0, kit_1.ensureArray)(aliasees)
118
120
  .flatMap((a) => this.getAll(a))
119
121
  .map((a) => this.aliasStore.delete(a));
120
- return this.saveAliasStoreToFile();
122
+ return lockResponse.writeAndUnlock(aliasStoreToRawFileContents(this.aliasStore));
121
123
  }
122
124
  /**
123
125
  * Returns true if the provided alias exists
@@ -135,29 +137,19 @@ class AliasAccessor extends kit_1.AsyncOptionalCreatable {
135
137
  * go to the fileSystem and read the file, storing a copy in the class's store
136
138
  * if the file doesn't exist, create it empty
137
139
  */
138
- async readFileToAliasStore(useLock = false) {
139
- if (useLock) {
140
- await (0, proper_lockfile_1.lock)(this.fileLocation, lockRetryOptions_1.lockRetryOptions);
141
- }
140
+ async readFileToAliasStore() {
142
141
  try {
143
142
  this.aliasStore = fileContentsRawToAliasStore(await (0, promises_1.readFile)(this.fileLocation, 'utf-8'));
144
143
  }
145
144
  catch (e) {
146
145
  if (e instanceof Error && 'code' in e && typeof e.code === 'string' && ['ENOENT', 'ENOTDIR'].includes(e.code)) {
147
- this.aliasStore = new Map();
148
146
  await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.fileLocation), { recursive: true });
149
- await this.saveAliasStoreToFile();
150
- return;
147
+ this.aliasStore = new Map();
148
+ return (0, promises_1.writeFile)(this.fileLocation, aliasStoreToRawFileContents(this.aliasStore));
151
149
  }
152
- if (useLock)
153
- return unlockIfLocked(this.fileLocation);
154
150
  throw e;
155
151
  }
156
152
  }
157
- async saveAliasStoreToFile() {
158
- await (0, promises_1.writeFile)(this.fileLocation, aliasStoreToRawFileContents(this.aliasStore));
159
- return unlockIfLocked(this.fileLocation);
160
- }
161
153
  }
162
154
  exports.AliasAccessor = AliasAccessor;
163
155
  /**
@@ -166,11 +158,11 @@ exports.AliasAccessor = AliasAccessor;
166
158
  const getNameOf = (entity) => {
167
159
  if (typeof entity === 'string')
168
160
  return entity;
169
- const aliaseeName = entity.username;
170
- if (!aliaseeName) {
161
+ const { username } = entity;
162
+ if (!username) {
171
163
  throw new sfError_1.SfError(`Invalid aliasee, it must contain a user or username property: ${JSON.stringify(entity)}`);
172
164
  }
173
- return aliaseeName;
165
+ return username;
174
166
  };
175
167
  const fileContentsRawToAliasStore = (contents) => {
176
168
  const fileContents = JSON.parse(contents);
@@ -181,16 +173,4 @@ const aliasStoreToRawFileContents = (aliasStore) => JSON.stringify({ [exports.DE
181
173
  // exported for testSetup mocking
182
174
  const getFileLocation = () => (0, node_path_1.join)((0, node_os_1.homedir)(), global_1.Global.SFDX_STATE_FOLDER, exports.FILENAME);
183
175
  exports.getFileLocation = getFileLocation;
184
- const unlockIfLocked = async (fileLocation) => {
185
- try {
186
- await (0, proper_lockfile_1.unlock)(fileLocation);
187
- }
188
- catch (e) {
189
- // ignore the error. If it wasn't locked, that's what we wanted
190
- if (errorIsNotAcquired(e))
191
- return;
192
- throw e;
193
- }
194
- };
195
- const errorIsNotAcquired = (e) => e instanceof Error && 'code' in e && e.code === 'ENOTACQUIRED';
196
176
  //# sourceMappingURL=aliasAccessor.js.map
@@ -38,6 +38,7 @@ export declare class MyDomainResolver extends AsyncOptionalCreatable<MyDomainRes
38
38
  * executing the dns loookup.
39
39
  */
40
40
  resolve(): Promise<string>;
41
+ /** @deprecated there is nothing using this in forcedotcom, salesforcecli, or public github search */
41
42
  getCnames(): Promise<string[]>;
42
43
  /**
43
44
  * Used to initialize asynchronous components.
@@ -14,6 +14,7 @@ const ts_types_1 = require("@salesforce/ts-types");
14
14
  const kit_1 = require("@salesforce/kit");
15
15
  const logger_1 = require("../logger/logger");
16
16
  const sfdcUrl_1 = require("../util/sfdcUrl");
17
+ const global_1 = require("../global");
17
18
  const pollingClient_1 = require("./pollingClient");
18
19
  // Timeout for DNS lookup polling defaults to 3 seconds and should always be at least 3 seconds
19
20
  const DNS_TIMEOUT = Math.max(3, new kit_1.Env().getNumber('SFDX_DNS_TIMEOUT', 3));
@@ -69,6 +70,10 @@ class MyDomainResolver extends kit_1.AsyncOptionalCreatable {
69
70
  this.logger.debug('SF_DISABLE_DNS_CHECK set to true. Skipping DNS check...');
70
71
  return this.options.url.host;
71
72
  }
73
+ if (global_1.Global.isWeb) {
74
+ this.logger.debug('Web browser detected. Skipping DNS check...');
75
+ return this.options.url.host;
76
+ }
72
77
  // eslint-disable-next-line @typescript-eslint/no-this-alias
73
78
  const self = this;
74
79
  const pollingOptions = {
@@ -106,6 +111,7 @@ class MyDomainResolver extends kit_1.AsyncOptionalCreatable {
106
111
  const client = await pollingClient_1.PollingClient.create(pollingOptions);
107
112
  return (0, ts_types_1.ensureString)(await client.subscribe());
108
113
  }
114
+ /** @deprecated there is nothing using this in forcedotcom, salesforcecli, or public github search */
109
115
  async getCnames() {
110
116
  try {
111
117
  await this.resolve();
@@ -55,11 +55,10 @@ const lockInit = async (filePath) => {
55
55
  catch (err) {
56
56
  throw sfError_1.SfError.wrap(err);
57
57
  }
58
- const unlock = await (0, proper_lockfile_1.lock)(filePath, { ...lockRetryOptions_1.lockRetryOptions, realpath: false });
58
+ const unlock = await (0, proper_lockfile_1.lock)(filePath, { ...lockRetryOptions_1.lockRetryOptions, realpath: false, fs });
59
59
  return {
60
60
  writeAndUnlock: async (data) => {
61
- const logger = await logger_1.Logger.child('fileLocking.writeAndUnlock');
62
- logger.debug(`Writing to file: ${filePath}`);
61
+ (await logger_1.Logger.child('fileLocking.writeAndUnlock')).debug(`Writing to file: ${filePath}`);
63
62
  try {
64
63
  await fs.promises.writeFile(filePath, data);
65
64
  }
@@ -83,7 +82,7 @@ const lockInitSync = (filePath) => {
83
82
  catch (err) {
84
83
  throw sfError_1.SfError.wrap(err);
85
84
  }
86
- const unlock = (0, proper_lockfile_1.lockSync)(filePath, { ...lockRetryOptions_1.lockOptions, realpath: false });
85
+ const unlock = (0, proper_lockfile_1.lockSync)(filePath, { ...lockRetryOptions_1.lockOptions, realpath: false, fs });
87
86
  return {
88
87
  writeAndUnlock: (data) => {
89
88
  const logger = logger_1.Logger.childFromRoot('fileLocking.writeAndUnlock');
@@ -1,3 +1,4 @@
1
+ import fs from 'node:fs';
1
2
  export declare const lockOptions: {
2
3
  stale: number;
3
4
  };
@@ -7,5 +8,6 @@ export declare const lockRetryOptions: {
7
8
  maxTimeout: number;
8
9
  factor: number;
9
10
  };
11
+ fs: typeof fs;
10
12
  stale: number;
11
13
  };
@@ -5,12 +5,17 @@
5
5
  * Licensed under the BSD 3-Clause license.
6
6
  * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
7
  */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.lockRetryOptions = exports.lockOptions = void 0;
10
13
  // docs: https://github.com/moxystudio/node-proper-lockfile
14
+ const node_fs_1 = __importDefault(require("node:fs"));
11
15
  exports.lockOptions = { stale: 10_000 };
12
16
  exports.lockRetryOptions = {
13
17
  ...exports.lockOptions,
14
18
  retries: { retries: 10, maxTimeout: 1000, factor: 2 },
19
+ fs: node_fs_1.default, // lockfile supports injectable fs, which is needed for browser use
15
20
  };
16
21
  //# sourceMappingURL=lockRetryOptions.js.map
@@ -1 +1,2 @@
1
+ /** using globalThis.performance instead importing from node:perf_hooks so it works in browser */
1
2
  export declare const nowBigInt: () => bigint;
package/lib/util/time.js CHANGED
@@ -7,7 +7,7 @@ exports.nowBigInt = void 0;
7
7
  * Licensed under the BSD 3-Clause license.
8
8
  * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
9
9
  */
10
- const node_perf_hooks_1 = require("node:perf_hooks");
11
- const nowBigInt = () => BigInt((node_perf_hooks_1.performance.now() + node_perf_hooks_1.performance.timeOrigin) * 1_000_000);
10
+ /** using globalThis.performance instead importing from node:perf_hooks so it works in browser */
11
+ const nowBigInt = () => BigInt((globalThis.performance.now() + globalThis.performance.timeOrigin) * 1_000_000);
12
12
  exports.nowBigInt = nowBigInt;
13
13
  //# sourceMappingURL=time.js.map
@@ -26,6 +26,8 @@ export declare class WebOAuthServer extends AsyncCreatable<WebOAuthServer.Option
26
26
  private oauth2;
27
27
  private oauthConfig;
28
28
  private oauthError;
29
+ private app?;
30
+ private username?;
29
31
  constructor(options: WebOAuthServer.Options);
30
32
  /**
31
33
  * Returns the configured oauthLocalPort or the WebOAuthServer.DEFAULT_PORT
@@ -80,7 +82,31 @@ export declare class WebOAuthServer extends AsyncCreatable<WebOAuthServer.Option
80
82
  }
81
83
  export declare namespace WebOAuthServer {
82
84
  type Options = {
83
- oauthConfig: JwtOAuth2Config;
85
+ oauthConfig: JwtOAuth2Config & {
86
+ /**
87
+ * OAuth scopes to be requested for the access token.
88
+ *
89
+ * This should be a string with each scope separated by spaces:
90
+ * "refresh_token sfap_api chatbot_api web api"
91
+ *
92
+ * If not specified, all scopes assigned to the connected app are requested.
93
+ */
94
+ scope?: string;
95
+ };
96
+ } | {
97
+ oauthConfig: JwtOAuth2Config & {
98
+ /**
99
+ * OAuth scopes to be requested for the access token.
100
+ *
101
+ * This should be a string with each scope separated by spaces:
102
+ * "refresh_token sfap_api chatbot_api web api"
103
+ *
104
+ * If not specified, all scopes assigned to the connected app are requested.
105
+ */
106
+ scope?: string;
107
+ };
108
+ app: string;
109
+ username: string;
84
110
  };
85
111
  type Request = http.IncomingMessage & {
86
112
  query: {
@@ -73,9 +73,15 @@ class WebOAuthServer extends kit_1.AsyncCreatable {
73
73
  oauth2;
74
74
  oauthConfig;
75
75
  oauthError = new Error('Oauth Error');
76
+ app;
77
+ username;
76
78
  constructor(options) {
77
79
  super(options);
78
80
  this.oauthConfig = options.oauthConfig;
81
+ if ('app' in options) {
82
+ this.app = options.app;
83
+ this.username = options.username;
84
+ }
79
85
  }
80
86
  /**
81
87
  * Returns the configured oauthLocalPort or the WebOAuthServer.DEFAULT_PORT
@@ -113,14 +119,43 @@ class WebOAuthServer extends kit_1.AsyncCreatable {
113
119
  this.executeOauthRequest()
114
120
  .then(async (response) => {
115
121
  try {
116
- const authInfo = await authInfo_1.AuthInfo.create({
117
- oauth2Options: this.oauthConfig,
118
- oauth2: this.oauth2,
119
- });
120
- await authInfo.save();
121
- await this.webServer.handleSuccess(response);
122
- response.end();
123
- resolve(authInfo);
122
+ // Link app to an existing auth file.
123
+ if (this.app) {
124
+ const authInfo = await authInfo_1.AuthInfo.create({
125
+ oauth2Options: this.oauthConfig,
126
+ oauth2: this.oauth2,
127
+ });
128
+ const authFields = authInfo.getFields(true);
129
+ // get user authInfo and save app creds in `apps`
130
+ const userAuthInfo = await authInfo_1.AuthInfo.create({
131
+ username: this.username,
132
+ });
133
+ await userAuthInfo.save({
134
+ apps: {
135
+ [this.app]: {
136
+ clientId: (0, ts_types_1.ensureString)(authFields.clientId),
137
+ clientSecret: authFields.clientSecret,
138
+ accessToken: (0, ts_types_1.ensureString)(authFields.accessToken),
139
+ refreshToken: (0, ts_types_1.ensureString)(authFields.refreshToken),
140
+ oauthFlow: 'web',
141
+ },
142
+ },
143
+ });
144
+ await this.webServer.handleSuccess(response);
145
+ response.end();
146
+ resolve(authInfo);
147
+ }
148
+ else {
149
+ // new auth, create new file.
150
+ const authInfo = await authInfo_1.AuthInfo.create({
151
+ oauth2Options: this.oauthConfig,
152
+ oauth2: this.oauth2,
153
+ });
154
+ await authInfo.save();
155
+ await this.webServer.handleSuccess(response);
156
+ response.end();
157
+ resolve(authInfo);
158
+ }
124
159
  }
125
160
  catch (err) {
126
161
  this.oauthError = err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/core",
3
- "version": "8.11.4",
3
+ "version": "8.12.1-dev.0",
4
4
  "description": "Core libraries to interact with SFDX projects, orgs, and APIs.",
5
5
  "main": "lib/index",
6
6
  "types": "lib/index.d.ts",
@@ -77,7 +77,7 @@
77
77
  "@salesforce/ts-sinon": "^1.4.30",
78
78
  "@types/benchmark": "^2.1.5",
79
79
  "@types/fast-levenshtein": "^0.0.4",
80
- "@types/jsonwebtoken": "9.0.7",
80
+ "@types/jsonwebtoken": "9.0.9",
81
81
  "@types/proper-lockfile": "^4.1.4",
82
82
  "@types/semver": "^7.5.8",
83
83
  "benchmark": "^2.1.4",