text:
//
//	FLGradientButton.m
//	FishLamp
//
//	Created by Mike Fullerton on 2/14/11.
//	Copyright (c) 2013 GreenTongue Software LLC, Mike Fullerton.
//  The FishLamp Framework is released under the MIT License: http://fishlamp.com/license
//

#import "FLGradientButton.h"
#import "UIColor+FLMoreColors.h"
#import "FLPathUtilities.h"
#import "FLRoundRectWidget.h"
#import "FLButtonBackgroundWidget.h"
#import "FLBackButtonShapeWidget.h"
#import "UIImage+Resize.h"
#import "UIImage+Colorize.h"
#import "FLColorRange.h"

void FLGradientButtonColorRed(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(240,127,136,1.0) endColor:FLColorCreateWithRGBColorValues(231,53,66,1.0)] forControlState:UIControlStateNormal] ;
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:/*FLColorCreateWithRGBColorValues(225,1,17,1.0)*/ [UIColor fireEngineRed] endColor:FLColorCreateWithRGBColorValues(236,19,20,1.0)] forControlState:UIControlStateNormal];
	[button setLightText];
}
void FLGradientButtonColorPaleBlue(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(132,156,187,1.0) endColor:FLColorCreateWithRGBColorValues(89,119,162,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(72,106,154,1.0) endColor:FLColorCreateWithRGBColorValues(74,108,155,1.0)] forControlState:UIControlStateNormal];
	[button setLightText];
}
void FLGradientButtonColorBrightBlue(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(108,147,232,1.0) endColor:FLColorCreateWithRGBColorValues(57,112,224,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(34,96,221,1.0) endColor:FLColorCreateWithRGBColorValues(36,99,222,1.0)] forControlState:UIControlStateNormal];
	[button setLightText];
}
void FLGradientButtonDarkGray(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(112,112,114,1.0) endColor:FLColorCreateWithRGBColorValues(55,55,57,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(33,33,35,1.0) endColor:FLColorCreateWithRGBColorValues(71,71,73,1.0)] forControlState:UIControlStateNormal];
	[button setLightText];
}

void FLGradientButtonDarkGrayWithBlueTint(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(106,112,118,1.0) endColor:FLColorCreateWithRGBColorValues(55,63,71,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(43,50,59,1.0) endColor:FLColorCreateWithRGBColorValues(65,71,80,1.0)] forControlState:UIControlStateNormal];
	[button setLightText];
}
void FLGradientButtonBlack(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(112,112,112,1.0) endColor:FLColorCreateWithRGBColorValues(54,54,54,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:[UIColor blackColor] endColor:[UIColor blackColor]] forControlState:UIControlStateNormal];
	[button setLightText];
}

void FLGradientButtonColorGray(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(112,112,114,1.0) endColor:FLColorCreateWithRGBColorValues(55,55,57,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(33,33,35,1.0) endColor:FLColorCreateWithRGBColorValues(71,71,73,1.0)] forControlState:UIControlStateNormal];
}

void FLGradientButtonColorLightGray(id button)
{
	[[button backgroundWidget].topGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(112,112,114,1.0) endColor:FLColorCreateWithRGBColorValues(55,55,57,1.0)] forControlState:UIControlStateNormal];
	[[button backgroundWidget].bottomGradient setColorRange:[FLColorRange colorRange:FLColorCreateWithRGBColorValues(33,33,35,1.0) endColor:FLColorCreateWithRGBColorValues(71,71,73,1.0)] forControlState:UIControlStateNormal];
}

@implementation FLGradientButtonBaseClass

@synthesize buttonColorizer = _buttonColorizer;
@synthesize backgroundWidget = _backgroundWidget;
@synthesize shapeWidget = _shapeWidget;

- (void) applyTheme:(FLTheme*) theme
{
	[self setButtonColorizer:FLGradientButtonDarkGray];
}

- (void) _initGradientButton
{
	[super setBackgroundColor:[UIColor clearColor]];

	FLRoundRectWidget* widget = [FLRoundRectWidget widgetWithFrame:CGRectZero];
	self.shapeWidget = widget;
	self.shapeWidget.innerBorderColor = [UIColor gray15Color];

	self.backgroundWidget = [FLButtonBackgroundWidget buttonBackgroundWidget];
}

- (void) setAlpha:(CGFloat) alpha
{
	[super setAlpha:alpha];

	self.backgroundWidget.alpha = alpha;
}

- (void) setButtonColorizer:(FLButtonColorizerDeprecated) colorizer
{
	_buttonColorizer = colorizer;

	if(_buttonColorizer)
	{
		_buttonColorizer(self);
	}
}

- (void) drawRect:(CGRect) rect
{
	self.shapeWidget.cornerRadius = self.cornerRadius;
	self.backgroundWidget.alpha = self.isTranslucent ? 0.8f : 1.0;
	[super drawRect:rect];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
	if ((self = [super initWithCoder:aDecoder]))
	{
		[self _initGradientButton];
	}

	return self;
}

- (id)initWithFrame:(CGRect)aRect
{
	if ((self = [super initWithFrame: aRect]))
	{
		[self _initGradientButton];
	}

	return self;
}

- (id) initWithColor:(FLButtonColorizerDeprecated) color	title:(NSString*) title	 image:(UIImage*) image target:(id) target action:(SEL) action
{
	if((self = [super initWithFrame:CGRectZero]))
	{
		[self _initGradientButton];
		if(FLStringIsNotEmpty(title))
		{
			self.title = title;
		}
		self.image = image;
		[self setCallback:target action:action];
		if(color != nil)
		{
			self.wantsApplyTheme = NO;
		}
		self.buttonColorizer = color;
	}

	return self;
}

- (id) initWithColor:(FLButtonColorizerDeprecated) color title:(NSString*) title target:(id) target action:(SEL) action
{
	return [self initWithColor:color title:title image:nil target:target action:action];
}

- (id) initWithTitle:(NSString*) title	image:(UIImage*) image target:(id) target action:(SEL) action
{
	return [self initWithColor:nil title:title image:image target:target action:action];
}

- (id) initWithTitle:(NSString*) title target:(id) target action:(SEL) action
{
	return [self initWithColor:nil title:title image:nil target:target action:action];
}

- (void) dealloc
{
    FLRelease(_shapeWidget);
	FLRelease(_backgroundWidget);
	FLSuperDealloc();
}

- (void) setHighlighted:(BOOL) highlighted
{
	[super setHighlighted:highlighted];
	self.backgroundWidget.highlighted = highlighted;
}

//- (UIFont*) titleFont
//{
//	  switch(self.contentMode)
//	  {
//		  case FLButtonImageLayoutModeToolbar:
//			  return [UIFont boldSystemFontOfSize:[UIFont smallSystemFontSize]];
//
//		  case FLButtonImageLayoutModeCenteredSmallText:
//		  case FLButtonImageLayoutModeAlignLeft:
//			  return [UIFont boldSystemFontOfSize:[UIFont systemFontSize]];
//
//		  case FLButtonImageLayoutModeCentered:
//		  default:
//			  return [UIFont boldSystemFontOfSize:[UIFont buttonFontSize]];
//	  }
//
//	  return nil; // for compiler
//}

- (void) layoutSubviews
{
	if(_buttonColorizer)
	{
		_buttonColorizer(self);
	}
    _backgroundWidget.frame = self.bounds;
	_shapeWidget.frame = _backgroundWidget.frame;
    [super layoutSubviews];
}

- (void) setBackgroundWidget:(FLButtonBackgroundWidget*) widget
{
	if(_backgroundWidget)
	{
		[_backgroundWidget removeFromParent];
	}
	widget.contentMode = FLRectLayoutFill;
	FLSetObjectWithRetain(_backgroundWidget, widget);
	[self.shapeWidget addWidget:_backgroundWidget];
	[self setNeedsLayout];
}

- (void) setShapeWidget:(FLShapeWidget*) widget
{
    FLAssertIsNotNil(widget);

	if(_backgroundWidget)
	{
		[_backgroundWidget removeFromParent];
	}
	widget.contentMode = FLRectLayoutFill;
	FLSetObjectWithRetain(_shapeWidget, widget);
    FLAssertIsNotNil(self.rootWidget);

    [self.rootWidget addWidget:_shapeWidget];

	if(_backgroundWidget)
	{
		[widget addWidget:_backgroundWidget];
	}

	[self setNeedsLayout];
}

#define Height 12.0f
- (void) resizeImageToSmallSize
{
	if(self.image.size.height > Height)
	{
		[self.imageView resizeProportionally:[self.image proportionalBoundsWithMaxSize:CGSizeMake(1024.0f, Height)].size];
	}
	else
	{
		[self.imageView resizeToImageSize];
	}
}

@end

@implementation FLGradientButton

+ (FLGradientButton*) gradientButton:(FLButtonColorizerDeprecated) color	title:(NSString*) title target:(id) target action:(SEL) action
{
   return FLAutorelease([[FLGradientButton alloc] initWithColor:color title:title target:target action:action]);
}

+ (FLGradientButton*) gradientButton:(NSString*) title target:(id) target action:(SEL) action
{
   return FLAutorelease([[FLGradientButton alloc] initWithTitle:title target:target action:action]);
}

@end
#define kToolbarSpaceBetween 8.0f
#define kToolbarImageHeight 18.0f

@implementation FLToolbarButtonDeprecated

- (void) applyTheme:(FLTheme*) theme
{
    [super applyTheme:theme];
	self.buttonColorizer = FLGradientButtonBlack;
}

+ (FLToolbarButtonDeprecated*) toolbarButton:(FLButtonColorizerDeprecated) color title:(NSString*) title target:(id) target action:(SEL) action
{
   return FLAutorelease([[FLToolbarButtonDeprecated alloc] initWithColor:color title:title target:target action:action]);
}

+ (FLToolbarButtonDeprecated*) toolbarButton:(NSString*) title target:(id) target action:(SEL) action
{
   return FLAutorelease([[FLToolbarButtonDeprecated alloc] initWithTitle:title target:target action:action]);
}

- (void) updateImageAndTextViewPositions
{
	CGRect bounds = self.rectUsedForCenteringSubviews;

	CGRect imageFrame = self.imageView.frame;
	imageFrame.origin.x = kToolbarSpaceBetween;
	self.imageView.frameOptimizedForSize = FLRectCenterRectInRectVertically(bounds, imageFrame);
	self.titleLabel.frameOptimizedForSize =	 FLRectCenterRectInRectVertically(bounds, FLRectSetLeft(self.titleLabel.frame, FLRectGetRight(imageFrame) + kToolbarSpaceBetween/2));
}

- (void) setImage:(UIImage*) image
{
	[super setImage:image];
	[self resizeImageToSmallSize];
}

- (CGSize) defaultSize
{
	return CGSizeMake(80,32);
}

- (BOOL) setViewSizeToContentSize
{
	if(self.superview)
	{
		CGSize size = self.frame.size;
		if(FLStringIsNotEmpty(self.titleLabel.text))
		{
			[self.titleLabel sizeToFitText];
			size = self.titleLabel.frame.size;
		}

		if(self.image)
		{
			size.width += self.imageView.frame.size.width;
			size.width += kToolbarSpaceBetween/2;
		}

		size.width += (kToolbarSpaceBetween*2);
		size.height = 32.0f;

		if(DeviceIsPad())
		{
			size.width += 12.0f;
		}

		self.newFrame = FLRectSetSizeWithSize(self.frame, size);

		[self setNeedsLayout];
	}

	return YES;
}

- (UIFont*) titleFont
{
	return [UIFont boldSystemFontOfSize:[UIFont smallSystemFontSize]];
}

- (void) setTitle:(NSString*) string
{
	self.titleLabel.text = string;

	[self setViewSizeToContentSize];
	[self.superview setNeedsLayout];
	[self setNeedsLayout];
}

- (void) didMoveToSuperview
{
	[super didMoveToSuperview];
	if(self.superview)
	{
		[self setViewSizeToContentSize];
	}
}

- (void) setButtonColorizer:(FLButtonColorizerDeprecated) colorizer
{
	[super setButtonColorizer:colorizer];
	if(self.superview)
	{
		[self setViewSizeToContentSize];
	}
}
@end

@implementation FLBackButtonDeprecated

- (void) applyTheme:(FLTheme*) theme
{
    [super applyTheme:theme];
	self.buttonColorizer = FLGradientButtonBlack;
}

- (void) _initGradientButton
{
	[super _initGradientButton];
	self.shapeWidget = [FLBackButtonShapeWidget backButtonShapeWidget:12.0];
	self.shapeWidget.innerBorderColor = [UIColor gray15Color];
	self.shapeWidget.borderLineWidth = 1.0;
}

- (BOOL) setViewSizeToContentSize
{
	[super setViewSizeToContentSize];
	self.frame = FLRectAddWidth(self.frame, 10.0f);
	return YES;
}

- (CGRect) rectUsedForCenteringSubviews
{
	return FLRectInsetLeft(self.bounds, 8.0f);
}

+ (FLBackButtonDeprecated*) backButton:(FLButtonColorizerDeprecated) color	title:(NSString*) title target:(id) target action:(SEL) action
{
	return FLAutorelease([[FLBackButtonDeprecated alloc] initWithColor:color title:title target:target action:action]);
}

+ (FLBackButtonDeprecated*) backButton:(NSString*) title target:(id) target action:(SEL) action
{
	return FLAutorelease([[FLBackButtonDeprecated alloc] initWithTitle:title target:target action:action]);
}
@end

@implementation FLMenuButtonDeprecated

- (void) updateImageAndTextViewPositions
{
	CGRect bounds = self.rectUsedForCenteringSubviews;

	self.imageView.frameOptimizedForSize = FLRectCenterRectInRectVertically(bounds,
		FLRectSetLeft(self.imageView.frame /*FLRectScale(_imageView.frame, 0.84f)*/,
		bounds.size.width * 0.10f));

	self.titleLabel.frameOptimizedForSize =
		FLRectCenterRectInRectVertically(bounds,
			FLRectSetLeft(self.titleLabel.frame, bounds.size.width * 0.20));
}

- (UIFont*) titleFont
{
	return self.image == nil ? [super titleFont] : [UIFont boldSystemFontOfSize:[UIFont smallSystemFontSize]];
}

+ (FLMenuButtonDeprecated*) menuButton:(FLButtonColorizerDeprecated) color	title:(NSString*) title target:(id) target action:(SEL) action
{
	return FLAutorelease([[FLMenuButtonDeprecated alloc] initWithColor:color title:title target:target action:action]);
}

+ (FLMenuButtonDeprecated*) menuButton:(NSString*) title target:(id) target action:(SEL) action
{
	return FLAutorelease([[FLMenuButtonDeprecated alloc] initWithTitle:title target:target action:action]);
}

- (void) setImage:(UIImage*) image
{
	[super setImage:image];
	[self resizeImageToSmallSize];
}

- (CGSize) defaultSize
{
	return CGSizeMake(80,40);
}

@end

@implementation FLSmallButtonDeprecated

- (CGSize) defaultSize {
	return CGSizeMake(80,32);
}

- (void) setImage:(UIImage*) image {
	[super setImage:image];
	[self resizeImageToSmallSize];
}

- (UIFont*) titleFont {
	return [UIFont boldSystemFontOfSize:[UIFont smallSystemFontSize]];
}

+ (FLSmallButtonDeprecated*) smallButton:(FLButtonColorizerDeprecated) color title:(NSString*) title target:(id) target action:(SEL) action {
	return FLAutorelease([[FLSmallButtonDeprecated alloc] initWithColor:color title:title target:target action:action]);
}

+ (FLSmallButtonDeprecated*) smallButton:(NSString*) title target:(id) target action:(SEL) action {
	return FLAutorelease([[FLSmallButtonDeprecated alloc] initWithTitle:title target:target action:action]);
}

@end

@implementation FLFatButtonDeprecated

- (void) applyTheme:(FLTheme*) theme {
    self.buttonColorizer = FLGradientButtonDarkGray;
}

- (void) updateImageAndTextViewPositions {
	CGRect bounds = self.rectUsedForCenteringSubviews;

	self.imageView.frameOptimizedForSize =
			FLRectOptimizedForViewSize(
			FLRectCenterOnPoint(
				FLRectMakeWithSize(self.image.size),
				CGPointMake(bounds.size.width/2.0f, bounds.size.height * 0.33f)));

	self.titleLabel.frameOptimizedForSize = FLRectOptimizedForViewSize(
			FLRectCenterOnPoint(
					FLRectMakeWithSize(self.titleLabel.frame.size),
					CGPointMake(bounds.size.width/2.0f, bounds.size.height * 0.75f)));
}

- (UIFont*) titleFont {
	return [UIFont boldSystemFontOfSize:[UIFont systemFontSize]];
}

+ (FLFatButtonDeprecated*) fatButton:(FLButtonColorizerDeprecated) color
                               title:(NSString*) title
                               image:(UIImage*) image
                              target:(id) target
                              action:(SEL) action {
	return FLAutorelease([[FLFatButtonDeprecated alloc] initWithColor:color title:title image:image target:target action:action]);
}

+ (FLFatButtonDeprecated*) fatButton:(NSString*) title
                               image:(UIImage*) image
                              target:(id) target
                              action:(SEL) action {
	return FLAutorelease([[FLFatButtonDeprecated alloc] initWithColor:nil title:title image:image target:target action:action]);
}
@end


text:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fab="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#673AB7">

    <com.getbase.floatingactionbutton.FloatingActionButton
        android:id="@+id/pink_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        fab:fab_icon="@drawable/ic_fab_star"
        fab:fab_colorNormal="@color/pink"
        fab:fab_colorPressed="@color/pink_pressed"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:visibility="invisible" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:id="@+id/speed"
        android:layout_centerInParent="true"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"

        android:textSize="65dp"
        android:textColor="#fff"
        android:text="0.0 m/s"
        android:visibility="visible"
        android:textAlignment="center" />

    <com.getbase.floatingactionbutton.AddFloatingActionButton
        android:id="@+id/semi_transparent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/pink_icon"
        fab:fab_plusIconColor="@color/white"
        fab:fab_colorNormal="@color/blue_semi_transparent"
        fab:fab_colorPressed="@color/blue_semi_transparent_pressed"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:visibility="invisible" />

    <com.getbase.floatingactionbutton.FloatingActionButton
        android:id="@+id/setter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/semi_transparent"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:visibility="invisible" />

    <com.getbase.floatingactionbutton.AddFloatingActionButton
        android:id="@+id/normal_plus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        fab:fab_plusIconColor="@color/half_black"
        fab:fab_colorNormal="@color/white"
        fab:fab_colorPressed="@color/white_pressed"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginBottom="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp"
        android:visibility="invisible" />

    <com.getbase.floatingactionbutton.FloatingActionsMenu
        android:id="@+id/right_labels"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_above="@id/normal_plus"
        android:layout_marginLeft="16dp"
        android:layout_marginStart="16dp"
        fab:fab_addButtonColorNormal="@color/white"
        fab:fab_addButtonColorPressed="@color/white_pressed"
        fab:fab_addButtonPlusIconColor="@color/half_black"
        fab:fab_addButtonSize="mini"
        fab:fab_labelStyle="@style/menu_labels_style"
        fab:fab_labelsPosition="right"
        android:visibility="invisible">

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_title="Label on the right"
            fab:fab_colorPressed="@color/white_pressed"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_size="mini"
            fab:fab_title="Another one on the right"
            fab:fab_colorPressed="@color/white_pressed"/>

    </com.getbase.floatingactionbutton.FloatingActionsMenu>

    <com.getbase.floatingactionbutton.FloatingActionsMenu
        android:id="@+id/multiple_actions"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        fab:fab_addButtonColorNormal="@color/white"
        fab:fab_addButtonColorPressed="@color/white_pressed"
        fab:fab_addButtonPlusIconColor="@color/half_black"
        fab:fab_labelStyle="@style/menu_labels_style"
        android:layout_marginBottom="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginEnd="16dp">

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:id="@+id/action_a"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_title="Help me!"
            fab:fab_icon="@drawable/location"
            fab:fab_colorPressed="@color/white_pressed"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:id="@+id/action_b"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_title="Help someone!"
            fab:fab_icon="@drawable/fab_sos"
            fab:fab_colorPressed="@color/white_pressed"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:id="@+id/action_c"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_title="View my Karma"
            fab:fab_icon="@drawable/yang"
            fab:fab_colorPressed="@color/white_pressed"/>
    </com.getbase.floatingactionbutton.FloatingActionsMenu>

    <com.getbase.floatingactionbutton.FloatingActionsMenu
        android:id="@+id/multiple_actions_down"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        fab:fab_addButtonColorNormal="@color/white"
        fab:fab_addButtonColorPressed="@color/white_pressed"
        fab:fab_addButtonSize="mini"
        fab:fab_addButtonPlusIconColor="@color/half_black"
        fab:fab_expandDirection="down"
        fab:fab_labelStyle="@style/menu_labels_style"
        android:layout_marginTop="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginEnd="16dp"
        android:visibility="invisible"
        android:background="#0099cc">

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_colorPressed="@color/white_pressed"
            fab:fab_size="mini"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:id="@+id/button_remove"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_colorPressed="@color/white_pressed"
            fab:fab_title="Click to remove"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:id="@+id/button_gone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_colorPressed="@color/white_pressed"
            android:background="@color/pink"
            android:theme="@android:style/Holo.Light.SegmentedButton" />

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:id="@+id/action_enable"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_title="Set bottom menu enabled/disabled"
            fab:fab_colorPressed="@color/white_pressed"/>

    </com.getbase.floatingactionbutton.FloatingActionsMenu>

    <com.getbase.floatingactionbutton.FloatingActionsMenu
        android:id="@+id/multiple_actions_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/multiple_actions_down"
        android:layout_toStartOf="@+id/multiple_actions_down"
        android:layout_alignParentTop="true"
        fab:fab_addButtonColorNormal="@color/white"
        fab:fab_addButtonColorPressed="@color/white_pressed"
        fab:fab_addButtonSize="mini"
        fab:fab_addButtonPlusIconColor="@color/half_black"
        fab:fab_addButtonStrokeVisible="false"
        fab:fab_expandDirection="left"
        android:layout_marginTop="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginEnd="16dp"
        android:visibility="invisible">

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_colorPressed="@color/white_pressed"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            fab:fab_colorNormal="@color/white"
            fab:fab_colorPressed="@color/white_pressed"
            fab:fab_size="mini"/>

        <com.getbase.floatingactionbutton.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            fab:fab_colorNormal="@color/white"
            fab:fab_colorPressed="@color/white_pressed"
            fab:fab_size="mini"/>

    </com.getbase.floatingactionbutton.FloatingActionsMenu>

    <com.getbase.floatingactionbutton.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/setter_drawable"
        android:layout_above="@id/setter"
        android:layout_centerHorizontal="true"
        android:visibility="invisible" />
</RelativeLayout>

redpajama_set_name: RedPajamaGithub


text:
from zope import interface
from . import ISAResultRowModel

@interface.implementer(ISAResultRowModel)
class SAResultRowModel(object):
    def first(self, iface, row):
        for col in row:
            if iface.providedBy(col):
                return col
        raise ValueError("unable to find provider for {} in collection {}".format(iface, row))


text:
using System;

namespace NBitcoin.BouncyCastle.Crypto
{
    /**
     * this exception is thrown if a buffer that is meant to have output
     * copied into it turns out to be too short, or if we've been given
     * insufficient input. In general this exception will Get thrown rather
     * than an ArrayOutOfBounds exception.
     */
    internal class DataLengthException
        : CryptoException
    {
        /**
        * base constructor.
        */
        public DataLengthException()
        {
        }

        /**
         * create a DataLengthException with the given message.
         *
         * @param message the message to be carried with the exception.
         */
        public DataLengthException(
            string message)
            : base(message)
        {
        }

        public DataLengthException(
            string message,
            Exception exception)
            : base(message, exception)
        {
        }
    }
}


text: import 'bootstrap/dist/js/bootstrap';


text:
function CreateDate(time) {
  var date = new $Date();
  date.setTime(time);
  return date;
}

var kApiFunctionCache = new InternalArray();
var functionCache = kApiFunctionCache;

function Instantiate(data, name) {
  if (!%IsTemplate(data)) return data;
  var tag = %GetTemplateField(data, kApiTagOffset);
  switch (tag) {
    case kFunctionTag:
      return InstantiateFunction(data, name);
    case kNewObjectTag:
      var Constructor = %GetTemplateField(data, kApiConstructorOffset);
      // Note: Do not directly use a function template as a condition, our
      // internal ToBoolean doesn't handle that!
      var result = typeof Constructor === 'undefined' ?
          {} : new (Instantiate(Constructor))();
      ConfigureTemplateInstance(result, data);
      result = %ToFastProperties(result);
      return result;
    default:
      throw 'Unknown API tag <' + tag + '>';
  }
}

function InstantiateFunction(data, name) {
  // We need a reference to kApiFunctionCache in the stack frame
  // if we need to bail out from a stack overflow.
  var cache = kApiFunctionCache;
  var serialNumber = %GetTemplateField(data, kApiSerialNumberOffset);
  var isFunctionCached =
   (serialNumber in cache) && (cache[serialNumber] != kUninitialized);
  if (!isFunctionCached) {
    try {
      cache[serialNumber] = null;
      var fun = %CreateApiFunction(data);
      if (name) %FunctionSetName(fun, name);
      var flags = %GetTemplateField(data, kApiFlagOffset);
      var doNotCache = flags & (1 << kDoNotCacheBit);
      if (!doNotCache) cache[serialNumber] = fun;
      if (flags & (1 << kRemovePrototypeBit)) {
        %FunctionRemovePrototype(fun);
      } else {
        var prototype = %GetTemplateField(data, kApiPrototypeTemplateOffset);
        // Note: Do not directly use an object template as a condition, our
        // internal ToBoolean doesn't handle that!
        fun.prototype = typeof prototype === 'undefined' ?
            {} : Instantiate(prototype);
        if (flags & (1 << kReadOnlyPrototypeBit)) {
          %FunctionSetReadOnlyPrototype(fun);
        }
        %SetProperty(fun.prototype, "constructor", fun, DONT_ENUM);
        var parent = %GetTemplateField(data, kApiParentTemplateOffset);
        // Note: Do not directly use a function template as a condition, our
        // internal ToBoolean doesn't handle that!
        if (!(typeof parent === 'undefined')) {
          var parent_fun = Instantiate(parent);
          %SetPrototype(fun.prototype, parent_fun.prototype);
        }
      }
      ConfigureTemplateInstance(fun, data);
      if (doNotCache) return fun;
    } catch (e) {
      cache[serialNumber] = kUninitialized;
      throw e;
    }
  }
  return cache[serialNumber];
}

function ConfigureTemplateInstance(obj, data) {
  var properties = %GetTemplateField(data, kApiPropertyListOffset);
  if (!properties) return;
  // Disable access checks while instantiating the object.
  var requires_access_checks = %DisableAccessChecks(obj);
  try {
    for (var i = 1; i < properties[0];) {
      var length = properties[i];
      if (length == 3) {
        var name = properties[i + 1];
        var prop_data = properties[i + 2];
        var attributes = properties[i + 3];
        var value = Instantiate(prop_data, name);
        %SetProperty(obj, name, value, attributes);
      } else if (length == 5) {
        var name = properties[i + 1];
        var getter = properties[i + 2];
        var setter = properties[i + 3];
        var attribute = properties[i + 4];
        var access_control = properties[i + 5];
        %SetAccessorProperty(
            obj, name, getter, setter, attribute, access_control);
      } else {
        throw "Bad properties array";
      }
      i += length + 1;
    }
  } finally {
    if (requires_access_checks) %EnableAccessChecks(obj);
  }
}


text:
@import url("http://fonts.googleapis.com/css?family=Lato:100,300,400,700,900,400italic");
@import url("http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.css");
html,
body {
  height: 100%;
  overflow: hidden;
}
.book-container {
  height: 100%;
  position: relative;
}
.book-control {
  display: block;
  padding: 15px 0;
}
.book-content {
  display: block;
  position: absolute;
  top: 60px;
  bottom: 0;
  left: 0;
  right: 0;
}
.book-content .chapter-nav,
.book-content .chapter-content {
  height: 100%;
  overflow: auto;
}
.ani-fade-show-hide {
  -webkit-transition: all linear 0.5s;
  transition: all linear 0.5s;
}
.ani-fade-show-hide.ng-hide {
  opacity: 0;
}
/*
  We're using CSS transitions for when
  the enter and move events are triggered
  for the element that has the .repeated-item
  class
*/
.repeated-item.ng-enter,
.repeated-item.ng-move {
  -webkit-transition: 0.5s linear all;
  -moz-transition: 0.5s linear all;
  -o-transition: 0.5s linear all;
  transition: 0.5s linear all;
  opacity: 0;
}
/*
 The ng-enter-active and ng-move-active
 are where the transition destination properties
 are set so that the animation knows what to
 animate.
*/
.repeated-item.ng-enter.ng-enter-active,
.repeated-item.ng-move.ng-move-active {
  opacity: 1;
}
/*
  We're using CSS keyframe animations for when
  the leave event is triggered for the element
  that has the .repeated-item class
*/
.repeated-item.ng-leave {
  -webkit-animation: 0.5s my_animation;
  -moz-animation: 0.5s my_animation;
  -o-animation: 0.5s my_animation;
  animation: 0.5s my_animation;
}
@keyframes my_animation {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
/*
  Unfortunately each browser vendor requires
  its own definition of keyframe animation code...
*/
@-webkit-keyframes my_animation {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
@-moz-keyframes my_animation {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
@-o-keyframes my_animation {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}


text:
namespace ql {
	//! Component for items that can be equipped.
	struct equipment {
		//! "Slots" into a body part.
		struct tab {
			//! The type of part this tab can slot into.
			body_part::tag tag;
			//! The body part this tab is slotted into or nullopt if none.
			std::optional<id> o_part_id;
		};

		reg_ptr reg;

		id id;

		//! The ID of the bearer of this equipment, or nullopt if not equipped.
		std::optional<ql::id> o_bearer_id;

		//! The list of "tabs" that "slot" into the bearer's body parts.
		std::vector<tab> tabs;

		//! The action points required to equip this.
		action equip_cost;

		//! The action points required to unequip this.
		action unequip_cost;

		//! Whether the item is currently equipped to some being.
		auto equipped() const -> bool {
			return o_bearer_id.has_value();
		}

		//! Equips this to the being with ID @p bearer_id.
		auto equip(ql::id bearer_id) -> void;

		//! If equipped, unequips from the bearer, incurring costs.
		auto unequip() -> void;

		//! If equipped, immediately unequips from the bearer, without incurring costs.
		auto forced_unequip() -> void;
	};

	auto make_equipment( //
		reg& reg,
		id equipment_id,
		std::optional<id> o_bearer_id,
		std::vector<equipment::tab> tabs,
		action equip_cost,
		action unequip_cost) -> id;
}


text:
layout: post
title:  "Jekyll 3.0.x (portable)"
date:   2016-02-02 18:00:00
categories: jekyll
---
As described in my previous post, running [Jekyll](http://jekyllrb.com) on Windows is not an easy task.

## Jekyll for GitHub Pages
Now with the latest [update from GitHub](https://pages.github.com/versions/) it became a little easier.

### Prepare

You have to download following:

1. [ruby 2.1.7](http://dl.bintray.com/oneclick/rubyinstaller/ruby-2.1.7-i386-mingw32.7z)
2. [DevKit 4.7.2](http://dl.bintray.com/oneclick/rubyinstaller/DevKit-mingw64-32-4.7.2-20130224-1151-sfx.exe)

### Environment

1. create a new folder `github-pages` that will contain our new GitHub Pages environment
2. extact ruby to `github-pages` and rename the created folder to `ruby`
3. extact devkit to `github-pages` into a new `devkit` folder

### Config

In your `github-pages` folder create a new text file `setpath.cmd`:

```
@ECHO OFF
ECHO Adding Jekyll to PATH...

SET RUBY_PATH=%~dp0ruby
SET DEVKIT_PATH=%~dp0devkit

SET PATH=%RUBY_PATH%\bin;%DEVKIT_PATH%\bin;%DEVKIT_PATH%\mingw\bin;%PATH%
```

### Jekyll

Now we can install Jekyll and it's dependencies:

1. open a command prompt (`cmd`)
2. execute `setpath.cmd`
3. execute `gem install activesupport`
4. execute `gem install github-pages`
5. execute `gem install wdm`

### Done

We are finished and can test our GitHub Pages compatible portable Jekyll setup.


text:
import * as path from 'path';
import * as _ from 'underscore';
import { checkParamOrThrow } from 'apify-client/build/utils';
import { APIFY_PROXY_VALUE_REGEX } from 'apify-shared/regexs';
import { ENV_VARS, INTEGER_ENV_VARS, LOCAL_ENV_VARS, ACT_JOB_TERMINAL_STATUSES, ACT_JOB_STATUSES } from 'apify-shared/consts';
import log from './utils_log';
import { EXIT_CODES, COUNTRY_CODE_REGEX } from './constants';
import { initializeEvents, stopEvents } from './events';
import { apifyClient, addCharsetToContentType, sleep, snakeCaseToCamelCase, isAtHome, logSystemInfo, printOutdatedSdkWarning } from './utils';
import { maybeStringify } from './key_value_store';
import { ApifyCallError } from './errors';

const METAMORPH_AFTER_SLEEP_MILLIS = 300000;

// eslint-disable-next-line import/named,no-unused-vars,import/first
import { ActorRun } from './typedefs';

/**
 * Tries to parse a string with date.
 * Returns either a Date object or undefined
 *
 * @ignore
 */
const tryParseDate = (str) => {
    const unix = Date.parse(str);
    return unix > 0 ? new Date(unix) : undefined;
};

/**
 * Waits for given run to finish. If "waitSecs" is reached then returns unfinished run.
 *
 * @ignore
 */
const waitForRunToFinish = async ({ actId, runId, token, waitSecs, taskId }) => {
    let updatedRun;

    const { acts } = apifyClient;
    const startedAt = Date.now();
    const shouldRepeat = () => {
        if (waitSecs && (Date.now() - startedAt) / 1000 >= waitSecs) return false;
        if (updatedRun && ACT_JOB_TERMINAL_STATUSES.includes(updatedRun.status)) return false;

        return true;
    };

    const getRunOpts = { actId, runId };
    if (token) getRunOpts.token = token;

    while (shouldRepeat()) {
        getRunOpts.waitForFinish = waitSecs
            ? Math.round(waitSecs - (Date.now() - startedAt) / 1000)
            : 999999;

        updatedRun = await acts.getRun(getRunOpts);

        // It might take some time for database replicas to get up-to-date,
        // so getRun() might return null. Wait a little bit and try it again.
        if (!updatedRun) await sleep(250);
    }

    if (!updatedRun) {
        throw new ApifyCallError({ id: runId, actId }, 'Apify.call() failed, cannot fetch actor run details from the server');
    }
    const { status } = updatedRun;
    if (
        status !== ACT_JOB_STATUSES.SUCCEEDED
        && status !== ACT_JOB_STATUSES.RUNNING
        && status !== ACT_JOB_STATUSES.READY
    ) {
        const message = taskId
            ? `The actor task ${taskId} invoked by Apify.call() did not succeed. For details, see https://my.apify.com/view/runs/${runId}`
            : `The actor ${actId} invoked by Apify.call() did not succeed. For details, see https://my.apify.com/view/runs/${runId}`;
        throw new ApifyCallError(updatedRun, message);
    }

    return updatedRun;
};

/**
 * Parses input and contentType and appends it to a given options object.
 * Throws if input is not valid.
 *
 * @ignore
 */
const addInputOptionsOrThrow = (input, contentType, options) => {
    options.contentType = contentType;

    // NOTE: this function modifies contentType property on options object if needed.
    options.body = maybeStringify(input, options);

    checkParamOrThrow(options.body, 'input', 'Buffer|String');
    checkParamOrThrow(options.contentType, 'contentType', 'String');

    options.contentType = addCharsetToContentType(options.contentType);
};

/**
 * Parsed representation of the `APIFY_XXX` environmental variables.
 *
 * @typedef ApifyEnv
 * @property {string|null} actorId ID of the actor (APIFY_ACTOR_ID)
 * @property {string|null} actorRunId ID of the actor run (APIFY_ACTOR_RUN_ID)
 * @property {string|null} actorTaskId ID of the actor task (APIFY_ACTOR_TASK_ID)
 * @property {string|null} userId ID of the user who started the actor - note that it might be
 *   different than the owner ofthe actor (APIFY_USER_ID)
 * @property {string|null} token Authentication token representing privileges given to the actor run,
 *   it can be passed to various Apify APIs (APIFY_TOKEN)
 * @property {Date|null} startedAt Date when the actor was started (APIFY_STARTED_AT)
 * @property {Date|null} timeoutAt Date when the actor will time out (APIFY_TIMEOUT_AT)
 * @property {string|null} defaultKeyValueStoreId ID of the key-value store where input and output data of this
 *   actor is stored (APIFY_DEFAULT_KEY_VALUE_STORE_ID)
 * @property {string|null} defaultDatasetId ID of the dataset where input and output data of this
 *   actor is stored (APIFY_DEFAULT_DATASET_ID)
 * @property {number|null} memoryMbytes Amount of memory allocated for the actor,
 *   in megabytes (APIFY_MEMORY_MBYTES)
 */

/**
 * Returns a new {@link ApifyEnv} object which contains information parsed from all the `APIFY_XXX` environment variables.
 *
 * For the list of the `APIFY_XXX` environment variables, see
 * [Actor documentation](https://docs.apify.com/actor/run#environment-variables).
 * If some of the variables are not defined or are invalid, the corresponding value in the resulting object will be null.
 *
 * @memberof module:Apify
 * @function
 * @name getEnv
 * @returns {ApifyEnv}
 */
export const getEnv = () => {
    // NOTE: Don't throw if env vars are invalid to simplify local development and debugging of actors
    const env = process.env || {};
    const envVars = {};

    _.mapObject(ENV_VARS, (fullName, shortName) => {
        const camelCaseName = snakeCaseToCamelCase(shortName);
        let value = env[fullName];

        // Parse dates and integers.
        if (value && fullName.endsWith('_AT')) value = tryParseDate(value);
        else if (_.contains(INTEGER_ENV_VARS, fullName)) value = parseInt(value, 10);

        envVars[camelCaseName] = value || value === 0
            ? value
            : null;
    });

    return envVars;
};

/**
 * @callback UserFunc
 * @return {Promise<void>}
 */

/**
 * Runs the main user function that performs the job of the actor
 * and terminates the process when the user function finishes.
 *
 * **The `Apify.main()` function is optional** and is provided merely for your convenience.
 * It is mainly useful when you're running your code as an actor on the [Apify platform](https://apify.com/actors).
 * However, if you want to use Apify SDK tools directly inside your existing projects, e.g.
 * running in an [Express](https://expressjs.com/) server, on
 * [Google Cloud functions](https://cloud.google.com/functions)
 * or [AWS Lambda](https://aws.amazon.com/lambda/), it's better to avoid
 * it since the function terminates the main process when it finishes!
 *
 * The `Apify.main()` function performs the following actions:
 *
 *

 *
When running on the Apify platform (i.e. APIFY_IS_AT_HOME environment variable is set),
 *   it sets up a connection to listen for platform events.
 *   For example, to get a notification about an imminent migration to another server.
 *   See {@link Apify.events} for details.
 *

 *
It checks that either APIFY_TOKEN or APIFY_LOCAL_STORAGE_DIR environment variable
 *   is defined. If not, the functions sets APIFY_LOCAL_STORAGE_DIR to ./apify_storage
 *   inside the current working directory. This is to simplify running code examples.
 *

 *
It invokes the user function passed as the userFunc parameter.

 *
If the user function returned a promise, waits for it to resolve.

 *
If the user function throws an exception or some other error is encountered,
 *       prints error details to console so that they are stored to the log.

 *
Exits the Node.js process, with zero exit code on success and non-zero on errors.

 *

 *
 * The user function can be synchronous:
 *
 * ```javascript
 * Apify.main(() => {
 *   // My synchronous function that returns immediately
 *   console.log('Hello world from actor!');
 * });
 * ```
 *
 * If the user function returns a promise, it is considered asynchronous:
 * ```javascript
 * const { requestAsBrowser } = require('some-request-library');
 *
 * Apify.main(() => {
 *   // My asynchronous function that returns a promise
 *   return request('http://www.example.com').then((html) => {
 *     console.log(html);
 *   });
 * });
 * ```
 *
 * To simplify your code, you can take advantage of the `async`/`await` keywords:
 *
 * ```javascript
 * const request = require('some-request-library');
 *
 * Apify.main(async () => {
 *   // My asynchronous function
 *   const html = await request('http://www.example.com');
 *   console.log(html);
 * });
 * ```
 *
 * @param {UserFunc} userFunc User function to be executed. If it returns a promise,
 * the promise will be awaited. The user function is called with no arguments.
 *
 * @memberof module:Apify
 * @function
 * @name main
 */
export const main = (userFunc) => {
    if (!userFunc || typeof (userFunc) !== 'function') {
        throw new Error(`Apify.main() accepts a single parameter that must be a function (was '${userFunc === null ? 'null' : typeof (userFunc)}').`);
    }

    // Logging some basic system info (apify and apify-client version, NodeJS version, ...).
    logSystemInfo();

    // Log warning if SDK is outdated.
    printOutdatedSdkWarning();

    if (!process.env[ENV_VARS.LOCAL_STORAGE_DIR] && !process.env[ENV_VARS.TOKEN]) {
        const dir = path.join(process.cwd(), './apify_storage');
        process.env[ENV_VARS.LOCAL_STORAGE_DIR] = dir;
        log.warning(`Neither ${ENV_VARS.LOCAL_STORAGE_DIR} nor ${ENV_VARS.TOKEN} environment variable is set, defaulting to ${ENV_VARS.LOCAL_STORAGE_DIR}="${dir}"`); // eslint-disable-line max-len
    }

    // This is to enable unit tests where process.exit() is mocked and doesn't really exit the process
    // Note that mocked process.exit() might throw, so set exited flag before calling it to avoid confusion.
    let exited = false;
    const exitWithError = (err, exitCode, message) => {
        log.exception(err, message);
        exited = true;
        // console.log(`Exiting with code: ${exitCode}`);
        process.exit(exitCode);
    };

    // Set dummy interval to ensure the process will not be killed while awaiting empty promise:
    // await new Promise(() => {})
    // Such a construct is used for testing of actor timeouts and aborts.
    const intervalId = setInterval(_.noop, 9999999);

    // Using async here to have nice stack traces for errors
    const run = async () => {
        initializeEvents();
        try {
            await userFunc();

            stopEvents();
            clearInterval(intervalId);
            if (!exited) {
                process.exit(EXIT_CODES.SUCCESS);
            }
        } catch (err) {
            stopEvents();
            clearInterval(intervalId);
            if (!exited) {
                exitWithError(err, EXIT_CODES.ERROR_USER_FUNCTION_THREW, 'The function passed to Apify.main() threw an exception:');
            }
        }
    };

    run().catch((err) => {
        exitWithError(err, EXIT_CODES.ERROR_UNKNOWN, 'Unknown error occurred');
    });
};

let callMemoryWarningIssued = false;

/**
 * Runs an actor on the Apify platform using the current user account (determined by the `APIFY_TOKEN` environment variable),
 * waits for the actor to finish and fetches its output.
 *
 * By passing the `waitSecs` option you can reduce the maximum amount of time to wait for the run to finish.
 * If the value is less than or equal to zero, the function returns immediately after the run is started.
 *
 * The result of the function is an {@link ActorRun} object
 * that contains details about the actor run and its output (if any).
 * If the actor run fails, the function throws the {@link ApifyCallError} exception.
 *
 * If you want to run an actor task rather than an actor, please use the
 * {@link Apify#callTask} function instead.
 *
 * For more information about actors, read the
 * [documentation](https://docs.apify.com/actor).
 *
 * **Example usage:**
 *
 * ```javascript
 * const run = await Apify.call('apify/hello-world', { myInput: 123 });
 * console.log(`Received message: ${run.output.body.message}`);
 * ```
 *
 * Internally, the `call()` function invokes the
 * [Run actor](https://apify.com/docs/api/v2#/reference/actors/run-collection/run-actor)
 * and several other API endpoints to obtain the output.
 *
 * @param {string} actId
 *  Either `username/actor-name` or actor ID.
 * @param {object} [input]
 *  Input for the actor. If it is an object, it will be stringified to
 *  JSON and its content type set to `application/json; charset=utf-8`.
 *  Otherwise the `options.contentType` parameter must be provided.
 * @param {Object} [options={}]
 *   Object with the settings below:
 * @param {string} [options.contentType]
 *  Content type for the `input`. If not specified,
 *  `input` is expected to be an object that will be stringified to JSON and content type set to
 *  `application/json; charset=utf-8`. If `options.contentType` is specified, then `input` must be a
 *  `String` or `Buffer`.
 * @param {string} [options.token]
 *  User API token that is used to run the actor. By default, it is taken from the `APIFY_TOKEN` environment variable.
 * @param {number} [options.memoryMbytes]
 *  Memory in megabytes which will be allocated for the new actor run.
 *  If not provided, the run uses memory of the default actor run configuration.
 * @param {number} [options.timeoutSecs]
 *  Timeout for the actor run in seconds. Zero value means there is no timeout.
 *  If not provided, the run uses timeout of the default actor run configuration.
 * @param {string} [options.build]
 *  Tag or number of the actor build to run (e.g. `beta` or `1.2.345`).
 *  If not provided, the run uses build tag or number from the default actor run configuration (typically `latest`).
 * @param {string} [options.waitSecs]
 *  Maximum time to wait for the actor run to finish, in seconds.
 *  If the limit is reached, the returned promise is resolved to a run object that will have
 *  status `READY` or `RUNNING` and it will not contain the actor run output.
 *  If `waitSecs` is null or undefined, the function waits for the actor to finish (default behavior).
 * @param {boolean} [options.fetchOutput=true]
 *  If `false` then the function does not fetch output of the actor.
 * @param {boolean} [options.disableBodyParser=false]
 *  If `true` then the function will not attempt to parse the
 *  actor's output and will return it in a raw `Buffer`.
 * @param {Array<object>} [options.webhooks] Specifies optional webhooks associated with the actor run, which can be used
 *  to receive a notification e.g. when the actor finished or failed, see
 *  [ad hook webhooks documentation](https://docs.apify.com/webhooks/ad-hoc-webhooks) for detailed description.
 * @returns {Promise<ActorRun>}
 * @throws {ApifyCallError} If the run did not succeed, e.g. if it failed or timed out.
 *
 * @memberof module:Apify
 * @function
 * @name call
 */
export const call = async (actId, input, options = {}) => {
    const { acts, keyValueStores } = apifyClient;

    checkParamOrThrow(actId, 'actId', 'String');
    checkParamOrThrow(options, 'opts', 'Object');

    // Common options.
    const { token } = options;
    checkParamOrThrow(token, 'token', 'Maybe String');

    // RunAct() options.
    const { build, memory, timeoutSecs, webhooks } = options;
    let { memoryMbytes } = options;
    const runActOpts = {
        actId,
    };

    // HOTFIX: Some old actors use "memory", so we need to keep them working for a while
    if (memory && !memoryMbytes) {
        memoryMbytes = memory;
        if (!callMemoryWarningIssued) {
            callMemoryWarningIssued = true;
            // eslint-disable-next-line max-len
            log.warning('The "memory" option of the Apify.call() function has been deprecated and will be removed in the future. Use "memoryMbytes" instead!');
        }
    }

    checkParamOrThrow(build, 'build', 'Maybe String');
    checkParamOrThrow(memoryMbytes, 'memoryMbytes', 'Maybe Number');
    checkParamOrThrow(timeoutSecs, 'timeoutSecs', 'Maybe Number');
    checkParamOrThrow(webhooks, 'webhooks', 'Maybe Array');
    if (token) runActOpts.token = token;
    if (build) runActOpts.build = build;
    if (memoryMbytes) runActOpts.memory = memoryMbytes;
    if (timeoutSecs >= 0) runActOpts.timeout = timeoutSecs; // Zero is valid value!
    if (webhooks) runActOpts.webhooks = webhooks;
    if (input) addInputOptionsOrThrow(input, options.contentType, runActOpts);

    // Run actor.
    const { waitSecs } = options;
    checkParamOrThrow(waitSecs, 'waitSecs', 'Maybe Number');
    const run = await acts.runAct(runActOpts);
    if (waitSecs <= 0) return run; // In this case there is nothing more to do.

    // Wait for run to finish.
    const updatedRun = await waitForRunToFinish({
        actId,
        runId: run.id,
        token,
        waitSecs,
    });

    // Finish if output is not requested or run haven't finished.
    const { fetchOutput = true } = options;
    if (!fetchOutput || updatedRun.status !== ACT_JOB_STATUSES.SUCCEEDED) return updatedRun;

    // Fetch output.
    const { disableBodyParser = false } = options;
    checkParamOrThrow(disableBodyParser, 'disableBodyParser', 'Boolean');
    const output = await keyValueStores.getRecord({
        key: 'OUTPUT',
        storeId: updatedRun.defaultKeyValueStoreId,
        disableBodyParser,
    });

    return Object.assign({}, updatedRun, { output });
};

/**
 * Runs an actor task on the Apify platform using the current user account (determined by the `APIFY_TOKEN` environment variable),
 * waits for the task to finish and fetches its output.
 *
 * By passing the `waitSecs` option you can reduce the maximum amount of time to wait for the run to finish.
 * If the value is less than or equal to zero, the function returns immediately after the run is started.
 *
 * The result of the function is an {@link ActorRun} object
 * that contains details about the actor run and its output (if any).
 * If the actor run failed, the function fails with {@link ApifyCallError} exception.
 *
 * Note that an actor task is a saved input configuration and options for an actor.
 * If you want to run an actor directly rather than an actor task, please use the
 * {@link Apify#call} function instead.
 *
 * For more information about actor tasks, read the [documentation](https://docs.apify.com/tasks).
 *
 * **Example usage:**
 *
 * ```javascript
 * const run = await Apify.callTask('bob/some-task');
 * console.log(`Received message: ${run.output.body.message}`);
 * ```
 *
 * Internally, the `callTask()` function calls the
 * [Run task](https://apify.com/docs/api/v2#/reference/actor-tasks/run-collection/run-task)
 * and several other API endpoints to obtain the output.
 *
 * @param {string} taskId
 *  Either `username/task-name` or task ID.
 * @param {object} [input]
 *  Input overrides for the actor task. If it is an object, it will be stringified to
 *  JSON and its content type set to `application/json; charset=utf-8`.
 *  Otherwise the `options.contentType` parameter must be provided.
 *  Provided input will be merged with actor task input.
 * @param {Object} [options={}]
 *   Object with the settings below:
 * @param {string} [options.contentType]
 *  Content type for the `input`. If not specified,
 *  `input` is expected to be an object that will be stringified to JSON and content type set to
 *  `application/json; charset=utf-8`. If `options.contentType` is specified, then `input` must be a
 *  `String` or `Buffer`.
 * @param {string} [options.token]
 *  User API token that is used to run the actor. By default, it is taken from the `APIFY_TOKEN` environment variable.
 * @param {number} [options.memoryMbytes]
 *  Memory in megabytes which will be allocated for the new actor task run.
 *  If not provided, the run uses memory of the default actor run configuration.
 * @param {number} [options.timeoutSecs]
 *  Timeout for the actor task run in seconds. Zero value means there is no timeout.
 *  If not provided, the run uses timeout of the default actor run configuration.
 * @param {string} [options.build]
 *  Tag or number of the actor build to run (e.g. `beta` or `1.2.345`).
 *  If not provided, the run uses build tag or number from the default actor run configuration (typically `latest`).
 * @param {string} [options.waitSecs]
 *  Maximum time to wait for the actor task run to finish, in seconds.
 *  If the limit is reached, the returned promise is resolved to a run object that will have
 *  status `READY` or `RUNNING` and it will not contain the actor run output.
 *  If `waitSecs` is null or undefined, the function waits for the actor task to finish (default behavior).
 * @param {Array<object>} [options.webhooks] Specifies optional webhooks associated with the actor run, which can be used
 *  to receive a notification e.g. when the actor finished or failed, see
 *  [ad hook webhooks documentation](https://docs.apify.com/webhooks/ad-hoc-webhooks) for detailed description.
 * @returns {Promise<ActorRun>}
 * @throws {ApifyCallError} If the run did not succeed, e.g. if it failed or timed out.
 *
 * @memberof module:Apify
 * @function
 * @name callTask
 */
export const callTask = async (taskId, input, options = {}) => {
    const { tasks, keyValueStores } = apifyClient;

    checkParamOrThrow(taskId, 'taskId', 'String');
    checkParamOrThrow(options, 'opts', 'Object');

    // Common options.
    const { token } = options;
    checkParamOrThrow(token, 'token', 'Maybe String');

    // Run task options.
    const { build, memoryMbytes, timeoutSecs, webhooks } = options;
    const runTaskOpts = { taskId };
    checkParamOrThrow(build, 'build', 'Maybe String');
    checkParamOrThrow(memoryMbytes, 'memoryMbytes', 'Maybe Number');
    checkParamOrThrow(timeoutSecs, 'timeoutSecs', 'Maybe Number');
    checkParamOrThrow(webhooks, 'webhooks', 'Maybe Array');
    if (token) runTaskOpts.token = token;
    if (build) runTaskOpts.build = build;
    if (memoryMbytes) runTaskOpts.memory = memoryMbytes;
    if (timeoutSecs >= 0) runTaskOpts.timeout = timeoutSecs; // Zero is valid value!
    if (input) runTaskOpts.input = input;
    if (webhooks) runTaskOpts.webhooks = webhooks;

    // Start task.
    const { waitSecs } = options;
    checkParamOrThrow(waitSecs, 'waitSecs', 'Maybe Number');
    const run = await tasks.runTask(runTaskOpts);
    if (waitSecs <= 0) return run; // In this case there is nothing more to do.

    // Wait for run to finish.
    const updatedRun = await waitForRunToFinish({
        actId: run.actId,
        runId: run.id,
        token,
        waitSecs,
        taskId,
    });

    // Finish if output is not requested or run haven't finished.
    const { fetchOutput = true } = options;
    if (!fetchOutput || updatedRun.status !== ACT_JOB_STATUSES.SUCCEEDED) return updatedRun;

    // Fetch output.
    const { disableBodyParser = false } = options;
    checkParamOrThrow(disableBodyParser, 'disableBodyParser', 'Boolean');
    const output = await keyValueStores.getRecord({
        key: 'OUTPUT',
        storeId: updatedRun.defaultKeyValueStoreId,
        disableBodyParser,
    });

    return Object.assign({}, updatedRun, { output });
};

/**
 * Transforms this actor run to an actor run of a given actor. The system stops the current container and starts the new container
 * instead. All the default storages are preserved and the new input is stored under the `INPUT-METAMORPH-1` key in the same default key-value store.
 *
 * @param {string} targetActorId
 *  Either `username/actor-name` or actor ID of an actor to which we want to metamorph.
 * @param {object} [input]
 *  Input for the actor. If it is an object, it will be stringified to
 *  JSON and its content type set to `application/json; charset=utf-8`.
 *  Otherwise the `options.contentType` parameter must be provided.
 * @param {Object} [options={}]
 *   Object with the settings below:
 * @param {string} [options.contentType]
 *  Content type for the `input`. If not specified,
 *  `input` is expected to be an object that will be stringified to JSON and content type set to
 *  `application/json; charset=utf-8`. If `options.contentType` is specified, then `input` must be a
 *  `String` or `Buffer`.
 * @param {string} [options.build]
 *  Tag or number of the target actor build to metamorph into (e.g. `beta` or `1.2.345`).
 *  If not provided, the run uses build tag or number from the default actor run configuration (typically `latest`).
 * @returns {Promise<void>}
 *
 * @memberof module:Apify
 * @function
 * @name metamorph
 */
export const metamorph = async (targetActorId, input, options = {}) => {
    // Use optionsCopy here as maybeStringify() may override contentType
    const optionsCopy = Object.assign({}, options);
    const { acts } = apifyClient;

    checkParamOrThrow(targetActorId, 'targetActorId', 'String');
    checkParamOrThrow(optionsCopy, 'opts', 'Object');
    checkParamOrThrow(optionsCopy.build, 'options.build', 'Maybe String');
    checkParamOrThrow(optionsCopy.contentType, 'options.contentType', 'Maybe String');

    const actorId = process.env[ENV_VARS.ACTOR_ID];
    const runId = process.env[ENV_VARS.ACTOR_RUN_ID];
    if (!actorId) throw new Error(`Environment variable ${ENV_VARS.ACTOR_ID} must be provided!`);
    if (!runId) throw new Error(`Environment variable ${ENV_VARS.ACTOR_RUN_ID} must be provided!`);

    if (input) {
        input = maybeStringify(input, optionsCopy);
        checkParamOrThrow(input, 'input', 'Buffer|String');
        if (optionsCopy.contentType) optionsCopy.contentType = addCharsetToContentType(optionsCopy.contentType);
    }

    await acts.metamorphRun({
        actId: actorId,
        runId,
        targetActorId,
        contentType: optionsCopy.contentType,
        body: input,
        build: optionsCopy.build,
    });

    // Wait some time for container to be stopped.
    // NOTE: option.customAfterSleepMillis is used in tests
    await sleep(optionsCopy.customAfterSleepMillis || METAMORPH_AFTER_SLEEP_MILLIS);
};

/**
 * Constructs an Apify Proxy URL using the specified settings.
 * The proxy URL can be used from Apify actors, web browsers or any other HTTP
 * proxy-enabled applications.
 *
 * For more information, see the [Apify Proxy](https://my.apify.com/proxy) page in the app
 * or the [documentation](https://docs.apify.com/proxy).
 *
 * @param {Object} [options]
 *   Object with the props below:
 * @param {string} [options.password]
 *   User's password for the proxy. By default, it is taken from the `APIFY_PROXY_PASSWORD`
 *   environment variable, which is automatically set by the system when running the actors
 *   on the Apify cloud, or when using the [Apify CLI](https://github.com/apifytech/apify-cli)
 *   package and the user previously logged in (called `apify login`).
 * @param {string[]} [options.groups]
 *   Array of Apify Proxy groups to be used. If not provided, the proxy will select
 *   the groups automatically.
 * @param {string} [options.session]
 *   Apify Proxy session identifier to be used by the Chrome browser. All HTTP requests
 *   going through the proxy with the same session identifier will use the same target
 *   proxy server (i.e. the same IP address), unless using Residential proxies. The identifier
 *   can only contain the following characters: `0-9`, `a-z`, `A-Z`, `"."`, `"_"` and `"~"`.
 * @param {string} [options.country]
 *   If set and relevant proxies are available in your Apify account, all proxied requests will
 *   use IP addresses that are geolocated to the specified country. For example `GB` for IPs
 *   from Great Britain. Note that online services often have their own rules for handling
 *   geolocation and thus the country selection is a best attempt at geolocation, rather than
 *   a guaranteed hit. This parameter is optional, by default, each proxied request is assigned
 *   an IP address from a random country. The country code needs to be a two letter ISO country code. See the
 *   [full list of available country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements).
 *   This parameter is optional, by default, the proxy uses all available proxy servers from all countries.
 *
 * @returns {string} Returns the proxy URL, e.g. `http://auto:my_password@proxy.apify.com:8000`.
 *
 * @memberof module:Apify
 * @function
 * @name getApifyProxyUrl
 */
export const getApifyProxyUrl = (options = {}) => {
    // For backwards compatibility.
    // TODO: remove this when we release v1.0.0
    if (!options.groups && options.apifyProxyGroups) {
        log.warning('Parameter `apifyProxyGroups` of Apify.getApifyProxyUrl() is deprecated!!! Use `groups` instead!');
        options.groups = options.apifyProxyGroups;
    }
    if (!options.session && options.apifyProxySession) {
        log.warning('Parameter `apifyProxySession` of Apify.getApifyProxyUrl() is deprecated!!! Use `session` instead!');
        options.session = options.apifyProxySession;
    }

    const {
        groups,
        session,
        country,
        password = process.env[ENV_VARS.PROXY_PASSWORD],
        hostname = process.env[ENV_VARS.PROXY_HOSTNAME] || LOCAL_ENV_VARS[ENV_VARS.PROXY_HOSTNAME],
        port = parseInt(process.env[ENV_VARS.PROXY_PORT] || LOCAL_ENV_VARS[ENV_VARS.PROXY_PORT], 10),

        // This is used only internaly. Some other function calling this function use different naming for groups and session
        // parameters so we need to override this in error messages.
        groupsParamName = 'opts.groups',
        sessionParamName = 'opts.session',
        countryParamName = 'opts.country',
    } = options;

    const getMissingParamErrorMgs = (param, env) => `Apify Proxy ${param} must be provided as parameter or "${env}" environment variable!`;
    const throwInvalidProxyValueError = (param) => {
        throw new Error(`The "${param}" option can only contain the following characters: 0-9, a-z, A-Z, ".", "_" and "~"`);
    };
    const throwInvalidCountryCode = (code) => {
        throw new Error(`The "${code}" option must be a valid two letter country code according to ISO 3166-1 alpha-2`);
    };

    checkParamOrThrow(groups, groupsParamName, 'Maybe [String]');
    checkParamOrThrow(session, sessionParamName, 'Maybe Number | String');
    checkParamOrThrow(country, countryParamName, 'Maybe String');
    checkParamOrThrow(password, 'opts.password', 'String', getMissingParamErrorMgs('password', ENV_VARS.PROXY_PASSWORD));
    checkParamOrThrow(hostname, 'opts.hostname', 'String', getMissingParamErrorMgs('hostname', ENV_VARS.PROXY_HOSTNAME));
    checkParamOrThrow(port, 'opts.port', 'Number', getMissingParamErrorMgs('port', ENV_VARS.PROXY_PORT));

    let username;

    if (groups || session || country) {
        const parts = [];

        if (groups && groups.length) {
            if (!groups.every(group => APIFY_PROXY_VALUE_REGEX.test(group))) throwInvalidProxyValueError('groups');
            parts.push(`groups-${groups.join('+')}`);
        }
        if (session) {
            if (!APIFY_PROXY_VALUE_REGEX.test(session)) throwInvalidProxyValueError('session');
            parts.push(`session-${session}`);
        }
        if (country) {
            if (!COUNTRY_CODE_REGEX.test(country)) throwInvalidCountryCode(country);
            parts.push(`country-${country}`);
        }

        username = parts.join(',');
    } else {
        username = 'auto';
    }

    return `http://${username}:${password}@${hostname}:${port}`;
};

/**
 *
 * Creates an ad-hoc webhook for the current actor run, which lets you receive a notification when the actor run finished or failed.
 * For more information about Apify actor webhooks, please see the [documentation](https://docs.apify.com/webhooks).
 *
 * Note that webhooks are only supported for actors running on the Apify platform.
 * In local environment, the function will print a warning and have no effect.
 *
 * @param {Object} options
 * @param {string[]} options.eventTypes
 *   Array of event types, which you can set for actor run, see
 *   the [actor run events](https://docs.apify.com/webhooks/events#actor-run) in the Apify doc.
 * @param {string}  options.requestUrl
 *   URL which will be requested using HTTP POST request, when actor run will reach the set event type.
 * @param {string} [options.payloadTemplate]
 *   Payload template is a JSON-like string that describes the structure of the webhook POST request payload.
 *   It uses JSON syntax, extended with a double curly braces syntax for injecting variables `{{variable}}`.
 *   Those variables are resolved at the time of the webhook's dispatch, and a list of available variables with their descriptions
 *   is available in the [Apify webhook documentation](https://docs.apify.com/webhooks).
 *
 *   When omitted, the default payload template will be used.
 *   [See the docs for the default payload template](https://docs.apify.com/webhooks).
 * @param {string} [options.idempotencyKey]
 *   Idempotency key enables you to ensure that a webhook will not be added multiple times in case of
 *   an actor restart or other situation that would cause the `addWebhook()` function to be called again.
 *   We suggest using the actor run ID as the idempotency key. You can get the run ID by calling
 *   {@link Apify#getEnv} function.
 * @return {Promise<object>} The return value is the Webhook object.
 * For more information, see the [Get webhook](https://apify.com/docs/api/v2#/reference/webhooks/webhook-object/get-webhook) API endpoint.
 *
 * @memberof module:Apify
 * @function
 * @name addWebhook
 */
export const addWebhook = async ({ eventTypes, requestUrl, payloadTemplate, idempotencyKey }) => {
    checkParamOrThrow(eventTypes, 'eventTypes', '[String]');
    checkParamOrThrow(requestUrl, 'requestUrl', 'String');
    checkParamOrThrow(payloadTemplate, 'payloadTemplate', 'Maybe String');

    if (!isAtHome()) {
        log.warning('Apify.addWebhook() is only supported when running on the Apify platform. The webhook will not be invoked.');
        return;
    }

    const runId = process.env[ENV_VARS.ACTOR_RUN_ID];
    if (!runId) {
        throw new Error(`Environment variable ${ENV_VARS.ACTOR_RUN_ID} is not set!`);
    }

    return apifyClient.webhooks.createWebhook({
        webhook: {
            isAdHoc: true,
            eventTypes,
            condition: {
                actorRunId: runId,
            },
            requestUrl,
            payloadTemplate,
            idempotencyKey,
        },
    });
};


text:
import { configureRequestOptionsFromUrl, DownloadOptions, HttpExecutor } from "builder-util-runtime"
import { net } from "electron"
import { ensureDir } from "fs-extra-p"
import { RequestOptions } from "http"
import * as path from "path"

export type LoginCallback = (username: string, password: string) => void

export class ElectronHttpExecutor extends HttpExecutor<Electron.ClientRequest> {
  constructor(private readonly proxyLoginCallback?: (authInfo: any, callback: LoginCallback) => void) {
    super()
  }

  async download(url: string, destination: string, options: DownloadOptions): Promise<string> {
    if (options == null || !options.skipDirCreation) {
      await ensureDir(path.dirname(destination))
    }

    return await options.cancellationToken.createPromise<string>((resolve, reject, onCancel) => {
      this.doDownload({
        ...configureRequestOptionsFromUrl(url, {
          headers: options.headers || undefined,
        }),
        redirect: "manual",
      }, destination, 0, options, error => {
        if (error == null) {
          resolve(destination)
        }
        else {
          reject(error)
        }
      }, onCancel)
    })
  }

  public doRequest(options: any, callback: (response: any) => void): any {
    const request = net.request(options)
    request.on("response", callback)
    this.addProxyLoginHandler(request)
    return request
  }

  private addProxyLoginHandler(request: Electron.ClientRequest) {
    if (this.proxyLoginCallback != null) {
      request.on("login", this.proxyLoginCallback)
    }
  }

  protected addRedirectHandlers(request: any, options: RequestOptions, reject: (error: Error) => void, redirectCount: number, handler: (options: RequestOptions) => void) {
    request.on("redirect", (statusCode: number, method: string, redirectUrl: string) => {
      if (redirectCount > 10) {
        reject(new Error("Too many redirects (> 10)"))
        return
      }

      handler(HttpExecutor.prepareRedirectUrlOptions(redirectUrl, options))
    })
  }
}