Saturday 10 October 2020

React Native Development Environment Setup

 I choose to setup the react native development environment for iOS first since I already have Xcode installed.

  • React Native CLI (not using Expo since I may need to use native code)
  • React Native with TypeScript

Target OS: iOS

  • Node : already installed v12.9.0 using nvm v6.14.8
  • Watchman: su admin; brew install watchman
  • Xcode with command line tools : already installed
  • Cocoapods: already installed
Create react native project.
npx react-native init <project> --template react-native-template-typescript

Run Metro
cd ~/<project>; npx react-native start

Run iOS App
cd ~/<project>; npx react-native run-ios

Target OS: Android

  • brew cask install adoptopenjdk/openjdk/adoptopenjdk8
  • Install Android Studio
    • Android SDK Platform 29
    • Google APIs Intel x86 Atom System Image
    • Android SDK Build-Tools select 29.0.2
  • Add ANDROID_HOME environment variable to .profile and add PATH.
Run Metro
cd ~/<project>; npx react-native start

Run Android App
cd ~/<project>; npx react-native run-android

Wednesday 1 April 2020

Using GitHub from Xcode and Mac OS Command Prompt


I have the front end app developed in Xcode and the backend logic developed in NodeJS.  Therefore I will need to manage 2 independent repositories.

Using GitHub from Xcode 11

If you Xcode project is using local Git, you can create new remote GitHub repository directly in Xcode.  Follow this link.

Using GitHub from Mac OS Command Prompt

You need to create an empty private repository in GitHub manually first followed by pushing it from command prompt.
  1. Create Repository in GitHub manually.  Set it to private.
  2. Copy the clone url.  Example:
  3. On Mac, open the command prompt and go to the project folder.  Ensure that the git user has permission to the repository.  Check the user using git config
  4. Set the origin URL to the new repository.  git remote set-url origin
  5. If the local git repository doesn't have any existing remote.  You can use add.  git remote add origin
  6. Push to remote.  git push -u origin master
  7. Omit the -u parameter for subsequent push.
If you see this error, you can clear your credential first.
$ git push origin master
remote: Repository not found.
fatal: repository '' not found

Tuesday 12 February 2019

Migrating to ParseServer 3


ParseServer 3 comes with a few breaking changes and requires a few type of changes.  I am documenting my experience of migrating my App.  Since we are all coders, I illustrate the code before and after migration in diff output format.


My current code base do not use backbone style callbacks.  I make extensive use of promises, both sequential and parallel (i.e Parse.Promise.when).  When I do migration, I keep the existing promises and therefore I do not use need to use async function.  These are the main changes:
  • Change Parse.Promise to native Promise
  • to Promise.resolve
  • Parse.Promise.error to Promise.reject
  • Parse.Promise.when to Promise.all
  • Change cloud function
  • response.success("result") to return "result"
  • response.error("problem") to throw new Error("problem")
  • response.error(array) to throw array
  • Remove the redundant error handling block
    }, function(error) {


Parse.Cloud.define("getPlayer", function(request, response) {
  var player = request.params.player;

  // get session token for login user.  default to empty for non-login
  var sessionToken = {};
  if (request.user) {
    sessionToken = {sessionToken:request.user.getSessionToken()};

  var p1 = exports.getPlayer(sessionToken, player);

  return p1.then(function(result) {
  function(error) {

exports.getPlayer = function(options, player)
  var query = new Parse.Query("Players");
  query.equalTo("name", player);
  return query.find(options);


Parse.Cloud.define("getPlayer", (request) => {
  var player = request.params.player;

  // get session token for login user.  default to empty for non-login
  var sessionToken = {};
  if (request.user) {
    sessionToken = {sessionToken:request.user.getSessionToken()};

  var p1 = exports.getPlayer(sessionToken, player);

  return p1.then(function(result) {
    return result;

Diff output

-Parse.Cloud.define("getPlayer", function(request, response) {
+Parse.Cloud.define("getPlayer", (request) => {
   var player = request.params.player;
   // get session token for login user.  default to empty for non-login
@@ -935,37 +919,13 @@ Parse.Cloud.define("getPlayer", function(request, response) {
   var p1 = teamMod.getPlayerTeamElo(sessionToken, player);
   return p1.then(function(result) {  
-    response.success(result);
-  },
-  function(error) {
-    response.error(error);
+    return elo;

Cloud Function

Ordinary Cloud Function

Most of my cloud function are migrated with minor changes.  Example:

-Parse.Cloud.define("getGroup", function(request, response)
+Parse.Cloud.define("getGroup", (request) =>
   if (!userMod.isLogon(request))
-    response.error("Uh oh, you are not allowed to run this.");
-    return;
+    throw new Error("Uh oh, you are not allowed to run this.");

   var sessionToken = userMod.getSessionToken(request);
@@ -1976,23 +1886,22 @@ Parse.Cloud.define("getGroup", function(request, response)
   var p1 = groupMod.getGroup(sessionToken,username);

   return p1.then(function(result) {
-    response.success(result);
-  }, function(error) {
-    response.error(error);
+    return groups;
   }); // p2 = p1.then

 }); // getGroup

Returning Array as Error

If you need to return array of PFObject as error.  You would need to use throw array instead of throw new Error(array).

This is the diff output.
-      response.error(matches);
+      // Note: Use throw array instead of throw new Error(array) to pass
+      //       array of PFObjects to caller.
+      //
+      // Result of throw array is
+      // error: [ ParseObject { _objCount: 6, className: 'Match', id: '12345678' },
+      //  ParseObject { _objCount: 9, className: 'Match', id: '22345678' },
+      //  ParseObject { _objCount: 11, className: 'Match', id: '32345678' },
+      //  ParseObject { _objCount: 13, className: 'Match', id: '4234567' },
+      //  ParseObject { _objCount: 15, className: 'Match', id: '52345678' } ]
+      //
+      // Result of throw new Error(array) is
+      // error: [object Object],[object Object],[object Object],[object Object],[object Object]
+      throw matches;


Triggers are function such as beforeSave, afterSave, beforeDelete, afterDelete.  These functions are migrated in similar way as cloud functions.

-Parse.Cloud.beforeSave("Player", function(request, response) {
+Parse.Cloud.beforeSave("Player", (request) => {
   var player = request.object;

   // check if the object isNew (i.e have not been save before)
   // skip if the team record is not new
   if (!player.isNew()) {
     // no need to process and just return
-    response.success();
+    return;

@@ -905,12 +892,13 @@ Parse.Cloud.beforeSave("Player", function(request, response) {

   return p1.then(function(status) {
     if (status && status.length > 0) {
-      response.success();
+      return;
     } else {
-      response.error("Player.beforeSave failed to save Player for " + game + "," + circle + ".");
+      throw new Error("Player.beforeSave failed to save Player for " + game + "," + circle + ".");
   }); // p2 = p1.then

 }); // beforeSave Player


Wednesday 6 February 2019

Using Git Worktree, Branch and Merge on Multiple Heroku Environments

Git worktree is a handy feature when a developer is working on major upgrade, such as between Swift upgrade or any major version upgrade that are breaking existing API.

Create a Staging Environment on Heroku
If you are hosting your app on Heroku, you can create a staging app using heroku CLI (command line interface).  The following two command create elostaging remote and rename the auto-created app name, salty-garden-12345 to elostaging.
$ heroku create --remote elostaging
$ heroku apps:rename elostaging --app salty-garden-12345

Create a Branch and Worktree
Organise your directory structure
Git stores it depository in local .git folder and therefore it allows you to restructure your working folder easily.  My original working folder is ~/code/elo.  To allow easier organization of main and branches.  I re-structure my app, elo, working folders to the followings structure.

This can be done via 3 commands.
$ mv ~/code/elo main 
$ mkdir ~/code/elo
$ mv main ~/code/elo/

Create a Branch
$ cd ~/code/elo/main
$ git branch branch-4.0

Create a Worktree for the Branch
$ cd ~/code/elo/main
$ git worktree add ../branch-4.0 branch-4.0

Commit Changes to Branch
$ cd ~/code/elo/main
$ git add file1 file2
$ git commit -a "Updated"

Deploy from a Branch to Heroku Staging
You can deploy from a branch to staging with the following syntax.
$ cd ~/code/elo/branch-4.0
$ git push elostaging branch-4.0:master

Merge a Branch into Master
Suppose you are done with all works in the branch, you can merge the changes back to master using the following commands.  You will need to issue the merge command in the master worktree.

$ cd ~/code/elo/main
$ git merge branch-4.0
Updating xxxxxxa..yyyyyy2
 file1 |  9 +++++++++
 file2 | 16 ++++++++++++++++
 2 files changed, 29 insertions(+)

Delete Worktree
You can delete a worktree by deleting the folder and run
$ cd ~/code/elo
$ rm -rf branch-4.0
$ cd ~/code/elo/main
$ git worktree prune

Merge Production Fixes in Master to Branch
While you are working on a branch, you also make a hot fix in the master.  You can bring the changes in master back into the branch so that you have less work to merge in the future.

$ cd ~/code/elo/branch-4.0
$ git merge master
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Auto-merging xxx/yyy.js
Auto-merging xxx/zzz.js
Automatic merge failed; fix conflicts and then commit the result.

In this case, merge the conflict in readme.txt manually.  Test and then commit the changes into branch4.0.

git add readme.txt
$ git commit -m "merged production hotfix"

These are useful references on worktree, heroku, git branching and merging.
  1. SaltyCrane git-worktree-notes
  2. Useful commands from Matts
  3. Git Branching and Merging Basic
  4. Managing Multiple Environments for Heroku App

Saturday 22 December 2018

Adeline Note Privacy Policy

We collect personal and activity data, which may be linked.

We use technologies like cookies (small files stored on your browser), web beacons, or unique device identifiers to identify your computer or device so we can deliver a better experience. Our systems also log information like your browser, operating system and IP address.
We also may collect personally identifiable information that you provide to us, such as your name, address, phone number or email address. With your permission, we may also access other personal information on your device, such as your phone book, calendar or messages, in order to provide services to you. If authorized by you, we may also access profile and other information from services like Facebook.
Our systems may associate this personal information with your activities in the course of providing service to you (such as pages you view or things you click on or search for).

We collect or share your location only with permission.

In serving you, we may use or store your precise geographic location, if you give us permission to do so. We do not use or share this data for any other purpose. Many devices will indicate through an icon when location services are operating. We only share this location information with others as approved by you.

You can request to see your personal data.

You can sign into your account to see any personally identifiable information we have stored, such as your name, email, address or phone number. You can also contact us by email to request to see this information.

We may keep data indefinitely.

We may keep data indefinitely.

We don’t share your personal information with marketers.

We generally do not share personally identifiable information (such as name, address, email or phone) with other companies for marketing purposes.

No ad companies collect data through our service.

We do not allow advertising companies to collect data through our service for ad targeting.

You can ask privacy questions.

If you have any questions or concerns about our privacy policies, please contact us:

Vendors access data on our behalf.

In order to serve you, we may share your personal and anonymous information with other companies, including vendors and contractors. Their use of information is limited to these purposes, and subject to agreements that require them to keep the information confidential. Our vendors provide assurance that they take reasonable steps to safeguard the data they hold on our behalf, although data security cannot be guaranteed.

We take steps to protect personal information

Please do not upload confidential data in Adeline Note.  We take reasonable steps to secure your personally identifiable information against unauthorized access or disclosure. We encrypt transmission of data on pages where you provide payment information. However, no security or encryption method can be guaranteed to protect information from hackers or human error.
Information we collect may be stored or processed on computers located in any country where we do business.

Special situations may require disclosure of your data.

To operate the service, we also may make identifiable and anonymous information available to third parties in these limited circumstances: (1) with your express consent, (2) when we have a good faith belief it is required by law, (3) when we have a good faith belief it is necessary to protect our rights or property, or (4) to any successor or purchaser in a merger, acquisition, liquidation, dissolution or sale of assets. Your consent will not be required for disclosure in these cases, but we will attempt to notify you, to the extent permitted by law to do so.
You can review more privacy-related information.
This privacy policy was last updated on 22 Dec 2018. Our privacy policy may change from time to time. If we make any material changes to our policies, we will place a prominent notice on our website or application. If the change materially affects registered users, we will send a notice to you by email, push notification or text.

Friday 1 June 2018

CocoaPods: upgrade to 1.5.3 with ruby 2.5.1


See this error when running pod outdated.

Updating spec repo `master`
[!] Failed to connect to GitHub to update the CocoaPods/Specs specs repo - Please check if you are offline, or that GitHub is down

Root Cause

This is because "weak cryptographic standards removed" after 2018 February.

How to Upgrade

Follow the steps by

Run these series of steps to update openssl, then ruby, then cocoapods.

  1. Update brew
  2. Use brew to install latest openssl 2.5.1
  3. Use brew to install : brew install rbenv ruby-build
  4. Use rbenv to install ruby 2.5.1: rbenv install 2.5.1
  5. Use rbenv to set ruby version used : rbenv global 2.5.0
  6. Use rbenv to check ruby version: ruby --version
  7. Use gem to install cocoapods: gem install cocoapods -n /usr/local/bin
  8. Check cocoapods version : pod --version

Use rbenv to install ruby 2.3.7

Run these commands:

  1. Run: rbenv install 2.3.7
  2. Set the ruby version using: export RBENV_VERSION=2.3.7

Sunday 9 October 2016

Node: Installation and Upgrade

Node is installed using nvm and nodejs packages are install using npm.

Initial Installation

1.  Install nvm
curl -o- | bash
2. Install node version 4
nvm install 4

Upgrade NVM and Node version

1. Upgrade nvm using
curl -o- | bash
creationix/nvm/v0.32.0/ | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10007  100 10007    0     0   5040      0  0:00:01  0:00:01 --:--:--  5038
=> nvm is already installed in /Users/Seet/.nvm, trying to update using git
=> Source string already in /Users/Seet/.bash_profile
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="/Users/Seet/.nvm"
[ -s "$NVM_DIR/" ] && . "$NVM_DIR/"  # This loads nvm

2. Upgrade to stable node js 5 version
nvm install 5
######################################################################## 100.0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v5.12.0 (npm v3.8.6)
3. Uninstall node js 4
nvm uninstall 4
4. Set nvm alias to avoid this error: "N/A: version "N/A" is not yet installed."
nvm alias default v5.12.0
5. Useful command for debugging
> nvm alias
default -> v5.12.0
node -> stable (-> v5.12.0) (default)
stable -> 5.12 (-> v5.12.0) (default)
iojs -> N/A (default)
lts/* -> lts/argon (-> N/A)
lts/argon -> v4.6.0 (-> N/A)
> nvm debug
nvm --version: v0.32.0
$SHELL: /bin/bash
$HOME: /Users/Nebitrams
$NVM_DIR: '$HOME/.nvm'
nvm current: v5.12.0
which node: $NVM_DIR/versions/node/v5.12.0/bin/node
which iojs:
which npm: $NVM_DIR/versions/node/v5.12.0/bin/npm
npm config get prefix: $NVM_DIR/versions/node/v5.12.0
npm root -g: $NVM_DIR/versions/node/v5.12.0/lib/node_modules

Upgrade NPM

1. Upgrade nvm using
npm install -g npm@latest

Common Issue for Parse-server Upgrade

This is a common error during upgrade of parse-server.  After upgrade, the npm start may ends with error like this: "Cannot find module 'double-ended-queue'".

If you run npm list, you will see some missing modules such as the followings:
npm ERR! missing: double-ended-queue@^2.1.0-0, required by redis@2.8.0
npm ERR! missing: redis-commands@^1.2.0, required by redis@2.8.0
npm ERR! missing: redis-parser@^2.6.0, required by redis@2.8.0
 You can try to delete the package-lock.json and node_modules folder, following by re-install.

rm -rf package-lock.json node_modules/
npm i --no-optional
npm dedupe
npm up
Refer to for more information.