Updating an inherited Ionic application

At work, we received an incident where a 2 year old mobile app stopped working. That nobody on site developed. Doing a postmortem of what I did.

This was an application that was created by an external company specifically for a research project about three years ago. It was built by them, I was the Solution Architect on the project and didn't do any of the coding but overviewed the process. The company then gave us the code, deployed it to the iPhone and Android app stores, and wished us luck (we were given the development time as part of a package deal, it was a good use of the time we got). It was happily running to itself until yesterday. When the mobile app stopped working.

My skills at the start of this:

  • Javascript: Pretty good.
  • Typescript: I avoided it.
  • XCode: I am years out of date.
  • Node.js: I've compiled things, but never developed using it.
  • Ionic: nada.
  • React: A great idea, but I've no experience with it.

My understanding of the application:

It was built in Ionic so it could be compiled as an Android and an iPhone application from the same codebase. Ionic+React gives you an interface that functions like a native mobile application, but it's built with HTML and JavaScript. The application pulls information from a web-site to give up-to-date information to the application users, but it also has some baked in information you can peruse as well as a Journal that you can write information into.

OK.

Step 1: Analysis of Error

I download the iPhone app and when it runs, you get a black screen. Occasionally a flash of white text that I think reads that it's downloading content. My understanding of the app is the mobile sites connect to a back-end web site for content. We try to access the content site and we get errors. Aha, could the web site have been decommissioned? We spend some time in the cloud provider portal, we eventually find links to the article content and they work. So that's not the cause.

Step 2: App reviewing

While my iPhone version of the app is failing, someone with an Android starts it up and it works fine. OK. So it's not a Web side issue, and it's not a generic application issue. Something's changed in the iPhone environment, but we haven't uploaded a new version in years. Code review time.

Step 3: Code

I have the code on my machine from the git repository. I try a git pull and my account doesn't work on the repository anymore. OK. I compare the date of the code with the date of the last compiled release in the stores and it's close. So it should be OK. I make a copy of it just in case so I have a pristine version of the last known working codebase.

Reading the code, there's a pipeline-.yml file in the root, plus a src dir. Under src there's an App and a Web component. Good structure, logical. Since we know the web side is fine, let's try the App directory. There's the application code, and directories for android and ios specific work. As well as node modules and other things for an Ionic app.

Returning to the root directory, I did into the pipeline-ios.yml file. It is very useful, with the specific scripts and tasks for each stage of build and deploy. I grab the script to build and try it out.

1npm i -g @ionic/cli
2npm ci
3ionic capacitor build ios --no-open

It complains I don't have capacitor. What's that? Capacitor is "a native runtime built by the Ionic team". Ok, sure. I install it globally, but the npm and ionic here can't find it. I symlink the binary. Success. Start compiling

npm ERR! gyp ERR! UNCAUGHT EXCEPTION 
npm ERR! gyp ERR! stack TypeError: Cannot assign to read only property 'cflags' of object '#<Object>'
npm ERR! gyp ERR! stack     at createConfigFile (...node_modules/node-sass/node_modules/node-gyp/lib/configure.js:117:21)
npm ERR! gyp ERR! stack     at .../node_modules/node-sass/node_modules/node-gyp/lib/configure.js:84:9
npm ERR! gyp ERR! stack     at FSReqCallback.oncomplete (node:fs:186:23)

Well that's a lot of errors. OK, "read only property 'cflags'". My understanding is that when compiling, cflags is a common property telling specific flags to underlying c compilers of a variety of compiled languages. OK. So where is it? It's in /build. Which gets deleted after every run. Grr.

I run a compile and ctrl-c to stop it. I get lucky and the build dir isn't removed and it has the configure.js file in it. cflags is empty. Umm.. OK. Not sure what to do there. I look at the packages.json as that's where the node_modules writes its versioning. The versions are... VERY old. ionic/core v.5. It's up to 7 now. OK. The error is from gyp, and it's v7.1.2. Let's try a new gyp.

Step 4: Update gyp

This does not work. I install gyp globally, I update local files, every time I run the install it uses node-gyp v7.1.2. Well... fine. Let's go a new environment.

Step 5: Node 14 build environment

It would make sense for me to try and build this in the same environment it was originally built in, not my local node 20.0.0 environment. I grab a docker container for the right node version.

1docker pull node:14.17.3

Cool. I run it and link my code dir to it.

1docker run -i -v/.../code-dir:/code --rm node:14.17.3 

I then terminal into docker and run the build script. I have to run it a few times with tweaks to get the build environment right. I get right to the end and then:

ERR: Your Node.js version is v14.17.3. Node.js 14 reached end-of-life on 2023-04-30 and is no longer supported. Please update to the latest Node LTS version.

... well. Um.

That was a few days ago. Is the iPhone store checking for deprecated things? That's... weird. Um.. What do now?

Step 6: Update All The Things

I'll have to try running it upgraded to the newest everything. Node 20. Ionic 7. Let's go.

6.1: Node update

Already on 20.0.0 locally.

6.2: Node Packages update - Ionic

I start with Ionic updates, as that's the key component and it has a website with upgrade paths from Ionic 5 to Ionic 7. I follow the bouncing ball, going through the code to replace deprecated / changed functionality. When I get to Ionic 7, they tell me the ion slides packages was completely deprecated and replaced with swiper. Oh. I'll just get basic functionality for now.

6.3 Node Packages update - everything else

... I'm not proud, but everything that wasn't Ionic or Swiper I replaced the dependency with '*'. It took that to get it to compile even a little bit. I started getting errors with how I'd replaced swiper

Step 7: IonSlider to Swiper

Swiper and ionSlider were close enough to be familiar, but different enough to trip me up for over an hour. Eventually I managed to figure out how Swiper was different to ionSlider

Step 8: Local environment and Debugging.

The people who made this did do a good job. They included specific instructions on how to run a local, self-updating server (i.e., when you changed code, the server reloaded so you could just keep testing). Let's go.

npm i
ionic serve

Code was still broken BUT IT WAS RUNNING. I hadn't got swiper figured out yet, I had to remove some Library-style &lt;SwiperSlider> creation and do it in the code base itself (I'm guessing due to nesting of tags), but I got it to render! Badly. All the sliders were just stacked on top of each other. More working with styles, labels, etc. and it compiled and finally functioned correctly.

Step 9: Local simulator

I updated XCode and ran the compiler. It didn't compile.

[capacitor]         SwiftCompile normal arm64 Compiling\ AppDelegate.swift .../ios/App/App/AppDelegate.swift (in target 'App' from project 'App')
[capacitor]         SwiftCompile normal arm64 .../ios/App/App/AppDelegate.swift (in target 'App' from project 'App')

OK... I dug out the Chrome and looked into things... I couldn't find much. Looking into the error further I found

compiling for iOS 12.0, but module 'Capacitor' has a minimum deployment target of iOS 13.0

Oh. I loaded the project up in XCode and changed the iPhone minimum version to 13.0. Compiled successfully, and ran just like the server version.

Step 10: Now what?

I have a working, compiled iOS app on my machine. I now have to work with other teams to get the deploy and testflight happening.

Post-mortem.

  1. Having well documented code is a Gaia-send. This code had:
    • A generic readme.md that had:
      • Code snippets on how to set up your dev environment
      • A command list for getting things compiled and testing
      • Useful links to the web environments, backlogs, miro etc. (the miros etc. were missing but that was a years ago project)
      • Debugging guidance
    • Well structured code base with src, properties, etc.
    • Most of the code was self-documenting (ie. variable/ function names all described what they did/ were for). Specific bits got detailed comments.
  2. node is a pain in the butt for dependency management. I don't know how people do it.
  3. Docker is great for getting specific case environments for work, without polluting your home base computer setup
  4. I know enough JS to be dangerous in a variety of codebases.
  5. Inherited code will break, you need a game-plan for when that happens
  6. Access to systems atrophy over time. Keep information in a safe storage place and review over time. Especially at a handover - move all the things to where you know they are, don't rely on external holding of information.
  7. Versions, man. Versions everywhere.

Any advise to what I could've done better is appreciated.