stripe create user firebase cloud functions - ios

I am attempting to create a stripe user upon user creation for firebase, I keep receiving this error (error displayed below). the code for the function is also displayed below. if I need to post the database structure I will do so, I currently do not have any structure for stripe customer (this might be where issue occurs). if anyone can assist I would greatly appreciate it.
Error:
Error: Reference.child failed: First argument was an invalid path = "/stripe_customers/${data.uid}/customer_id". Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"
at Object.exports.validatePathString (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/core/util/validation.js:282:15)
at Object.exports.validateRootPathString (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/core/util/validation.js:293:13)
at Reference.child (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/api/Reference.js:72:30)
at Database.ref (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/cjs/src/api/Database.js:60:54)
at stripe.customers.create.then (/user_code/index.js:41:29)
at tryCatcher (/user_code/node_modules/stripe/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromise0 (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:614:10)
at Promise._settlePromises (/user_code/node_modules/stripe/node_modules/bluebird/js/release/promise.js:693:18)
at Async._drainQueue (/user_code/node_modules/stripe/node_modules/bluebird/js/release/async.js:133:16)
at Async._drainQueues (/user_code/node_modules/stripe/node_modules/bluebird/js/release/async.js:143:10)
at Immediate.Async.drainQueues (/user_code/node_modules/stripe/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:672:20)
at tryOnImmediate (timers.js:645:5)
at processImmediate [as _immediateCallback] (timers.js:617:5)
Functions:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const logging = require('#google-cloud/logging')();
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
//[START chargecustomer]
//charge the stripe customer whenever an amount is written to the realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}').onWrite((event) => {
const val = event.data.val();
if (val === null || val.id || val.error) return null;
return admin.database().ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
const amount = val.amount;
const idempotency_key = event.params.id;
let charge = {amount, currency, customer};
if (val.source !== null) charge.source = val.source;
return stripe.charges.create(charge, {idempotency_key});
}).then((response) => {
return event.data.adminRef.set(response);
}).catch((error) => {
return event.data.adminRef.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: events.params.userId});
});
});
// [end chargecustomer]]
// when user is created register them with stripe
exports.createStripeCustomer = functions.auth.user().onCreate((event) => {
const data = event.data;
return stripe.customers.create({
email: data.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${data.uid}/customer_id`).set(customer.id);
});
});
// add a payment source (card) for a user by writing a stripe payment source token to realtime database
exports.addPaymentSource =. functions.database.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((event) => {
const source = event.data.val();
if (sourve === null) return null;
return admin.database.ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source});
}).then((response) => {
return event.data.adminRef.parent.set(response);
}, (error) => {
return event.data.adminRef.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: event.params.userId});
});
});
// when a user deletes their account, clean up after the
exports.cleanupUser = functions.auth.user().onDelete((event) => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.del(customer.customer_id);
}).then(() => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).remove();
});
});
function reportError(err, context = {}) {
const logName = 'errors';
const lof = logging.log(logName);
const metadata = {
resource: {
type: 'cloud_function',
labels: {function_name: process.env.FUNCTION_NAME},
},
};
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function',
},
context: context,
};
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), (error) => {
if (error) {
reject(error);
}
resolve();
});
});
}
// end [reportError]
// sanitize the error message for the user
function userFacingMessage(error) {
returnerror.type ? error.message : 'an error occurred, developers have been altered';
}
Database Structure:

In your code you have this:
ref('/stripe_customers/${event.params.userId}/customer_id')
this ${event.params.userId} should give you the value of the wildcard, but since you are using ' it is including the $ in the path also. So you need to change it like this:
ref(`/stripe_customers/${event.params.userId}/customer_id`)
by changing ' to `

Related

Cordova Plugin Purchase - redirect after successful subscription

I am using this plugin (https://github.com/j3k0/cordova-plugin-purchase) to handle a in app subscription.
iap.validator = "https://validator.fovea.cc/v1/validate?appName=XXX";
//initiate initInAppPurchase function
useEffect(() => {
const init = async () => {
await initInAppPurchase();
}
init();
}, []);
//if on an ios or android device, then get product info
const initInAppPurchase = () => {
if ((isPlatform('ios')) || (isPlatform('android'))) {
iap.verbosity = iap.DEBUG;
iap.register({
id: "tpmonthly",
alias: "Monthly",
type: iap.PAID_SUBSCRIPTION
});
iap.ready(() => {
let product = iap.get('Monthly');
setPrice(product.price)
setProduct(product)
})
iap.refresh();
}
}
//if user clicks purchase button
const purchaseProduct = () => {
if (product.owned) {
alert('A subscription is currently active.')
} else {
iap.order('Monthly').then(() => {
iap.when("tpmonthly").approved((p: IAPProduct) => {
p.verify();
});
iap.when("tpmonthly").verified((p: IAPProduct) => {
p.finish();
history.push("/ios-signup/");
});
})
}
}
return (
<Button size="large" variant="outlined" onClick={purchaseProduct}>Subscribe Monthly for {productPrice}</Button>
);
What I am hoping to get is that once the subscription is verified that it then redirects the app to /ios-signup/ .. this is not happening.
Is this code correct? And why would it not redirect after p.finish?

Custom resolver React-hook-form

Can't get errors from resolver using react-hook-form custom resolvers. Trying to validate some fields dynamically. But if I submit form I can get errors fields
export const customResolver = (parameters) => {
return (values, _context, { fields, names }) => {
const transformedParameters = transformParameters(parameters);
const paramsIds = getParamsIds(transformedParameters);
const { name, value } = Object.values(fields)[0];
let errors = {};
if (names && names[0].length <= 3) {
// Validate single field
const textRequired = transformedParameters
.find(({ id }) => id === name)
.values.find(({ name: valueName }) => value === valueName).textRequired;
if (textRequired && !values[`${names} commentary`]) {
errors = {
[`${names} commentary`]: {
type: "required",
message: "Поле необходимо заполнить!",
},
};
}
} else {
// Validate onSubmit method
errors = Object.entries(values).reduce((acc, [id, fieldValue]) => {
if (paramsIds.includes(id) && !fieldValue) {
return {
...acc,
[id]: { type: "required", message: "Поле необходимо заполнить!" },
};
}
return acc;
}, {});
}
return { values, errors };
};
};
What did I do wrong?

How to refresh access token in electron app?google oauth2.0

I use this api to provide google login function for my electron app
https://github.com/googleapis/google-auth-library-nodejs
My access token expires after 3600 seconds
I don’t want my users to log in again after 3600 seconds
How can I make the token refresh automatically?
I try to use the document example code on the my app
But it doesn't seem to work
How can I get a new access_token
I try the code below to get a new access_token
But nothing happens
const { app, BrowserWindow, screen } = require('electron');
const fs = require('fs');
const { google } = require('googleapis'); // auth node js
googleOAuth2Login();
function googleOAuth2Login() {
const SCOPES = ['https://www.googleapis.com/auth/drive'];
const TOKEN_PATH = 'token.json';
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
authorize(JSON.parse(content), showAccessToken);
});
function authorize(credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, content) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(content));
callback(JSON.parse(content))
oAuth2Client.on('tokens', (tokens) => {
//this handle not work
if (tokens.refresh_token) {
// store the refresh_token in my database!
console.log(tokens.refresh_token);
}
console.log(tokens.access_token);
});
});
}
/**
* This method opens a new window to let users log-in the OAuth provider service,
* grant permissions to OAuth client service (this application),
* and returns OAuth code which can be exchanged for the real API access keys.
*
* #param {*} interactionWindow a window in which the user will have interaction with OAuth provider service.
* #param {*} authPageURL an URL of OAuth provider service, which will ask the user grants permission to us.
* #returns {Promise<string>}
*/
function getOAuthCodeByInteraction(interactionWindow, authPageURL) {
interactionWindow.loadURL(authPageURL, { userAgent: 'Chrome' });
return new Promise((resolve, reject) => {
const onclosed = () => {
reject('Interaction ended intentionally ;(');
};
interactionWindow.on('closed', onclosed);
interactionWindow.on('page-title-updated', (ev) => {
const url = new URL(ev.sender.getURL());
// console.log(url.searchParams)
if (url.searchParams.get('approvalCode')) {
console.log('allow')
interactionWindow.removeListener('closed', onclosed);
interactionWindow.close();
return resolve(url.searchParams.get('approvalCode'));
}
if ((url.searchParams.get('response') || '').startsWith('error=')) {
console.log('reject')
interactionWindow.removeListener('closed', onclosed);
interactionWindow.close();
return reject(url.searchParams.get('response'));
}
});
});
};
function executeAuthWindow(authWindow, authUrl) {
authWindow.setMenu(null);
authWindow.show();
return new Promise((resolve, reject) => {
getOAuthCodeByInteraction(authWindow, authUrl)
.then((res) => {
if (res != 'Interaction ended intentionally ;(') {
return resolve(res);
}
if (res == 'Interaction ended intentionally ;(') {
return reject('Fail:Authorization window colose');
}
}).catch((err) => {
if (err = 'error=access_denied') {
return reject('Fail: error=access_denied');
}
});
})
}
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
const authWindow = new BrowserWindow({
width: 600,
height: 800,
show: false,
'node-integration': false,
'web-security': false
});
executeAuthWindow(authWindow, authUrl)
.then((code) => {
//access_token: and refresh_token:
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
console.log('getToken')
console.log(token)
oAuth2Client.setCredentials(token);
console.log(oAuth2Client)
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
});
}).catch((err) => {
console.log(err)
})
}
// initOAuthClient
function showAccessToken(token) {
console.log(token)
}
}
credentials file
{
"installed": {
"client_id": "*******17079-*********gjlh6g2nnndhqotn3ij509k.apps.googleusercontent.com",
"project_id": "quickstart-**********",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "*********dNz3Gceo9F",
"redirect_uris": [
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}

TypeError: Cannot read property 'forEach' of undefined in twilio with angular

private async getAuthToken(name: string, room: string) {
const auth = await this.http
.post('http://localhost:8000/token', {name, room}).toPromise();
return auth;
}
async onRoom(){
const token = await this.getAuthToken(this.user, this.room);
try {
await connect(
token, {
name,
}).then(room =>
{
this.activeRoom = room;
console.log(this.activeRoom);
console.log(`Successfully joined a Room: ${room}`);
// Log new Participants as they connect to the Room
room.once('participantConnected', participant => {
console.log(`El participante "${participant.identity}" se está conectando`);
});
// Log Participants as they disconnect from the Room
room.once('participantDisconnected', participant => {
console.log(`El participante "${participant.identity}"se está desconectando`);
});
room.on('participantConnected', participant => {
console.log(`El participante ${participant.identity} se conecto`);
console.log(participant.track)
participant.track.forEach(track => {
console.log('foreach participant');
const localMediaContainer = document.getElementById('remote-media');
localMediaContainer.appendChild(track.attach());
});
participant.on('trackAdded', track => {
console.log('track added');
const localMediaContainer = document.getElementById('remote-media');
localMediaContainer.appendChild(track.attach());
})
});
room.on('participantDisconnected', participant => {
console.log(`El participante ${participant.identity} se desconecto` );
});
room.on('disconnected', room => {
room.localParticipant.tracks.forEach(track => {
const attachedElements = track.detach();
attachedElements.forEach(element => element.remove());
console.log(`La sala ${room} se ha desconectado con éxito`)
});
});
});
} catch (error) {
return console.error(`Unable to connect to Room: ${error.message}`);
}
}
I manage to connect rooms, but when another user enters the room I throw the following error
ERROR TypeError: Cannot read property 'forEach' of undefined
And also when I want to leave the room I throw the following error
ERROR TypeError: track.detach is not a function
The first call is to participant.track and the second is participant.tracks . Which one is correct ? The forEach error implies that participant.track does not exist.

Can I run `stencil push` command without prompt?

I'm trying to configure bitbucket pipelines with bigcommerce stencil.
The problem is the stencil push command asks some questions. I would like to auto-respond those questions.
Is that possible?
These are the questions prompted:
* Would you like to apply your theme to your store at http://xxxxxxx/? (y/N)
* Which variation would you like to apply?
- Light
- Bold
- Warm
You will need to make changes to the existing stencil-cli to make this work.
Stencil-cli uses the Commander package. My solution was to create an additional flag that would skip all the prompts if you supplied a variant name. This was created from stencil-cli version 1.13.1 so you may need to modify the example.
Inside /bin/stencil-push:
#!/usr/bin/env node
require('colors');
const apiHost = 'https://api.bigcommerce.com';
const dotStencilFilePath = './.stencil';
const options = { dotStencilFilePath };
const pkg = require('../package.json');
const Program = require('commander');
const stencilPush = require('../lib/stencil-push');
const versionCheck = require('../lib/version-check');
Program
.version(pkg.version)
.option('--host [hostname]', 'specify the api host', apiHost)
.option('-f, --file [filename]', 'specify the filename of the bundle to upload')
.option('-a, --activate [variationname]', 'specify the variation of the theme to activate')
.parse(process.argv);
if (!versionCheck()) {
return;
}
stencilPush(Object.assign({}, options, {
apiHost: Program.host || apiHost,
bundleZipPath: Program.file,
activate: Program.activate,
}), (err, result) => {
if (err) {
console.log('not ok'.red + ` -- ${err}`);
console.log('Please try again. If this error persists, please visit https://github.com/bigcommerce/stencil-cli/issues and submit an issue.');
} else {
console.log('ok'.green + ` -- ${result}`);
}
});
Inside /lib/stencil-push.js:
'use strict';
const _ = require('lodash');
const async = require('async');
const Bundle = require('./stencil-bundle');
const fs = require('fs');
const Inquirer = require('inquirer');
const os = require('os');
const ProgressBar = require('progress');
const themeApiClient = require('./theme-api-client');
const themePath = process.cwd();
const themeConfig = require('./theme-config').getInstance(themePath);
const uuid = require('uuid4');
const utils = {};
const Wreck = require('wreck');
const bar = new ProgressBar('Processing [:bar] :percent; ETA: :etas', {
complete: '=',
incomplete: ' ',
total: 100,
});
module.exports = utils;
function validateOptions(options, fields) {
options = options || {};
fields = fields || [];
fields.forEach(field => {
if (!_.has(options, field)) {
throw new Error(`${field} is required!`);
}
});
return options;
}
utils.readStencilConfigFile = (options, callback) => {
options = validateOptions(options, ['dotStencilFilePath']);
fs.readFile(options.dotStencilFilePath, { encoding: 'utf8' }, (err, data) => {
if (err) {
err.name = 'StencilConfigReadError';
return callback(err);
}
callback(null, Object.assign({}, options, {
config: JSON.parse(data),
}));
});
};
utils.getStoreHash = (options, callback) => {
options = validateOptions(options, ['config.normalStoreUrl']);
Wreck.get(`${options.config.normalStoreUrl}/admin/oauth/info`, { json: true, rejectUnauthorized: false }, (error, response, payload) => {
if (error) {
error.name = 'StoreHashReadError';
return callback(error);
}
if (response.statusCode !== 200 || !payload.store_hash) {
const err = new Error('Failed to retrieve store hash');
err.name = 'StoreHashReadError';
return callback(err);
}
callback(null, Object.assign({}, options, { storeHash: payload.store_hash }));
});
};
utils.getThemes = (options, callback) => {
const config = options.config;
themeApiClient.getThemes({
accessToken: config.accessToken,
apiHost: options.apiHost,
clientId: config.clientId,
storeHash: options.storeHash,
}, (error, result) => {
if (error) {
return callback(error);
}
callback(null, Object.assign({}, options, {
themes: result.themes,
}));
});
};
utils.generateBundle = (options, callback) => {
let bundle;
if (options.bundleZipPath) {
return async.nextTick(callback.bind(null, null, options));
}
bundle = new Bundle(themePath, themeConfig, themeConfig.getRawConfig(), {
dest: os.tmpdir(),
name: uuid(),
});
bundle.initBundle((err, bundleZipPath) => {
if (err) {
err.name = 'BundleInitError';
return callback(err);
}
callback(null, Object.assign(options, { bundleZipPath: options.bundleZipPath || bundleZipPath }));
});
};
utils.uploadBundle = (options, callback) => {
const config = options.config;
themeApiClient.postTheme({
accessToken: config.accessToken,
apiHost: options.apiHost,
bundleZipPath: options.bundleZipPath,
clientId: config.clientId,
storeHash: options.storeHash,
}, (error, result) => {
if (error) {
error.name = 'ThemeUploadError';
return callback(error);
}
callback(null, Object.assign({}, options, {
jobId: result.jobId,
themeLimitReached: !!result.themeLimitReached,
}));
});
};
utils.notifyUserOfThemeLimitReachedIfNecessary = (options, callback) => {
if (options.themeLimitReached) {
console.log('warning'.yellow + ` -- You have reached your upload limit. In order to proceed, you'll need to delete at least one theme.`);
}
return async.nextTick(callback.bind(null, null, options));
};
utils.promptUserToDeleteThemesIfNecessary = (options, callback) => {
if (!options.themeLimitReached) {
return async.nextTick(callback.bind(null, null, options));
}
const questions = [{
choices: options.themes.map(theme => ({
disabled: theme.is_active || !theme.is_private,
name: theme.name,
value: theme.uuid,
})),
message: 'Which theme(s) would you like to delete?',
name: 'themeIdsToDelete',
type: 'checkbox',
validate: (val) => {
if (val.length > 0) {
return true;
} else {
return 'You must delete at least one theme';
}
},
}];
Inquirer.prompt(questions, (answers) => {
callback(null, Object.assign({}, options, answers));
});
};
utils.deleteThemesIfNecessary = (options, callback) => {
const config = options.config;
if (!options.themeLimitReached) {
return async.nextTick(callback.bind(null, null, options));
}
async.parallel(options.themeIdsToDelete.map(themeId => {
return cb => {
themeApiClient.deleteThemeById(Object.assign({
accessToken: config.accessToken,
apiHost: options.apiHost,
clientId: config.clientId,
storeHash: options.storeHash,
themeId,
}, options), cb);
}
}), err => {
if (err) {
err.name = 'ThemeDeletionError';
return callback(err);
}
callback(null, options);
})
};
utils.uploadBundleAgainIfNecessary = (options, callback) => {
if (!options.themeLimitReached) {
return async.nextTick(callback.bind(null, null, options));
}
utils.uploadBundle(options, callback);
};
utils.notifyUserOfThemeUploadCompletion = (options, callback) => {
console.log('ok'.green + ' -- Theme Upload Finished');
return async.nextTick(callback.bind(null, null, options));
};
utils.markJobProgressPercentage = percentComplete => {
bar.update(percentComplete / 100);
};
utils.markJobComplete = () => {
utils.markJobProgressPercentage(100);
console.log('ok'.green + ' -- Theme Processing Finished');
};
utils.pollForJobCompletion = () => {
return async.retryable({
interval: 1000,
errorFilter: err => {
if (err.name === "JobCompletionStatusCheckPendingError") {
utils.markJobProgressPercentage(err.message);
return true;
}
return false;
},
times: Number.POSITIVE_INFINITY,
}, utils.checkIfJobIsComplete);
};
utils.checkIfJobIsComplete = (options, callback) => {
const config = options.config;
themeApiClient.getJob({
accessToken: config.accessToken,
apiHost: options.apiHost,
clientId: config.clientId,
storeHash: options.storeHash,
bundleZipPath: options.bundleZipPath,
jobId: options.jobId,
}, (error, result) => {
if (error) {
return callback(error);
}
utils.markJobComplete();
callback(null, Object.assign({}, options, result));
});
};
utils.promptUserWhetherToApplyTheme = (options, callback) => {
if (options.activate) {
callback(null, Object.assign({}, options, { applyTheme: true }));
} else {
const questions = [{
type: 'confirm',
name: 'applyTheme',
message: `Would you like to apply your theme to your store at ${options.config.normalStoreUrl}?`,
default: false,
}];
Inquirer.prompt(questions, answers => {
callback(null, Object.assign({}, options, { applyTheme: answers.applyTheme }));
});
};
};
utils.getVariations = (options, callback) => {
if (!options.applyTheme) {
return async.nextTick(callback.bind(null, null, options));
}
themeApiClient.getVariationsByThemeId({
accessToken: options.accessToken,
apiHost: options.apiHost,
clientId: options.clientId,
themeId: options.themeId,
storeHash: options.storeHash,
}, (error, result) => {
if (error) {
return callback(error);
};
if (options.activate !== true && options.activate !== undefined) {
const findVariation = result.variations.find(item => item.name === options.activate);
callback(null, Object.assign({}, options, { variationId: findVariation.uuid }));
} else if (options.activate === true) {
callback(null, Object.assign({}, options, { variationId: result.variations[0].uuid }));
} else {
callback(null, Object.assign({}, options, result));
};
});
};
utils.promptUserForVariation = (options, callback) => {
if (!options.applyTheme) {
return async.nextTick(callback.bind(null, null, options))
}
if (options.variationId) {
callback(null, options);
} else {
const questions = [{
type: 'list',
name: 'variationId',
message: 'Which variation would you like to apply?',
choices: options.variations.map(variation => ({ name: variation.name, value: variation.uuid })),
}];
Inquirer.prompt(questions, answers => {
console.log(answers);
callback(null, Object.assign({}, options, answers));
});
};
};
utils.requestToApplyVariationWithRetrys = () => {
return async.retryable({
interval: 1000,
errorFilter: err => {
if (err.name === "VariationActivationTimeoutError") {
console.log('warning'.yellow + ` -- Theme Activation Timed Out; Retrying...`);
return true;
}
return false;
},
times: 3,
}, utils.requestToApplyVariation);
};
utils.requestToApplyVariation = (options, callback) => {
if (!options.applyTheme) {
return async.nextTick(callback.bind(null, null, options));
}
themeApiClient.activateThemeByVariationId({
accessToken: options.accessToken,
apiHost: options.apiHost,
clientId: options.clientId,
storeHash: options.storeHash,
variationId: options.variationId,
}, (error, result) => {
if (error) {
return callback(error);
}
callback(null, Object.assign({}, options, result));
});
};
utils.notifyUserOfCompletion = (options, callback) => {
callback(null, 'Stencil Push Finished');
};
This allowed me to use something like stencil push --activate bold to specify a variation and skip all of the prompts.
As of version 1.15.1 this seems to be available with the -a, --activate [variationname] switch for stencil push
> stencil push -a "My Variant" worked for me
Thanks Nikita Puza!
It works like a charm. I applied the changes on stencil 1.14.1 version and the source code looks exactly the same.
The only difference is the second file is called stencil-push.utils.js instead of stencil-push.js

Resources