Nicolas BAPTISTA - Blog of a Parisian software architect

This blog contains 60 articles

N I C O L A S    B A P T I S T A

PERSONNAL BLOG

SOFTWARE ARCHITECT • JAVASCRIPT DEVELOPER • DESIGNER


LinkedIn Contact Github Remote OK Resume My Projects

XCode not compiling for Mac Catalyst because of an SDK / xcframework

Is it possible that in your project you was coding for iOS and iPad but you also want your application to build on MacOS and you can do it easily with Catalyst that allows you to run an iOS app on MacOS.
For your information, you can enable it on Apple Store Connect so your app on TestFlight will be available on MacOS.
But, you can also add it in XCode, this is what we will do in this small tutorial :

You can add MacOS destination to your mobile project in “General” tab

The only problem is that sometimes some SDK and frameworks are not compatible with MacOS Catalyst, so you may have such errors if your try to build or launch the app on MacOS :

Method 1 : disable build of framework in “Build Phases” for Mac Catalyst

Go in the “Build Phases” tab of your app in the section “Link Binary With Libraries”

Disable the platform Mac Catalyst for the frameworks causing the error. You have to go in the tab “Build Phases” > Link Binary With Library, then look to the framework that you want to not build for Mac Catalyst, near the Status you can see the arrow icon, click on it and uncheck “Allow any platform” and “Mac Catalyst”.

Method 2 : use cocoapods catalyst support

To solve this problem you can try to have a look on the repository of cocoapods-catalyst-support :

Step 1 : install the package

$ gem install cocoapods-catalyst-support

Step 2 : init

$ pod catalyst init

Step 3 : set the config in /ios/Podfile

In the ios folder (if you are on a react native project), open the Podfile file and after the pod catalyst init you can see that there are additional lines:

catalyst_configuration do
	# Uncomment the next line for a verbose output
	# verbose!

	# ios '<pod_name>' # This dependency will only be available for iOS
	# macos '<pod_name>' # This dependency will only be available for macOS
end

Modify this part with the SDK / frameworks that are a problem during the build.

Step 4 : Apply the changes

Apply the changes for the next build on XCode with these commands

$ pod catalyst validate     # check if your new configuration is valid
$ pod catalyst run             # apply the changes if valid

Method 3 : Build Settings for each Pod

If you still have errors you can also look in the “Build Settings” of the concerned Pod and disable “Derive Mac Catalyst Product Bundle Identifier” and “Supports Mac Catalyst” :

Generate IPA file from XCode

To generate a IPA file from XCode you need to archive your project, if you already did it before, you can open the organizer. If you never archived your project, you can go to Product > Archive and then it will open the Organizer when the compilation is complete.

Archive the project if you didn’t before
OR open the organizer if you already archived your project
Click on Distribute App
You can click on “Custom” and then “Next”
You should select “Development”
You can then select “All compatible device variants” in App Thinning
You can then select “Automatically manage signing”

Then you should be able to go to the final step

Export the project to IPA file with Export (select the right folder)

Integrate Intune SDK to React Native project

This article is about my experience with integration of Intune SDK to a react native project as there is no official SDK provided by Microsoft for React Native.

Copy the SDK content in the /ios folder

Go to https://github.com/msintuneappsdk/ms-intune-app-sdk-ios and download all the repository. Extract the content in a folder named “IntuneMAM” and put it in the /ios folder, same level as your *.xcodeproj file.

Run the configurator

In the root of the Intune SDK file, you should have the IntuneMAMConfigurator file that will be configuring the plist and entitlements automatically :

% ./IntuneMAMConfigurator -i ../PROJECTNAME/Info.plist -e ../PROJECTNAME/PROJECTNAME.entitlements 
2024-02-29 13:16:45.787 IntuneMAMConfigurator[36231:7276997] Success!!!

This is possible that you have rights issues (permission denied), to solve it you can do the usual chmod :

chmod 777 IntuneMAMConfigurator

Then, based on your needed, read this documentation: https://learn.microsoft.com/en-us/mem/intune/developer/app-sdk-ios-phase3

Adding extra dependencies

Add the following dependencies to be sure the SDK will work:

  • MessageUI.framework
  • Security.framework
  • CoreServices.framework
  • SystemConfiguration.framework
  • libsqlite3.tbd
  • libc++.tbd
  • ImageIO.framework
  • LocalAuthentication.framework
  • AudioToolbox.framework
  • QuartzCore.framework
  • WebKit.framework
  • MetricKit.framework
These libs should be added

Drag & drop the .xcframework folders

Open XCode and drag & drop the xcframework folders to “Frameworks, Libraries and Embedded Content”

Drag & Drop these 3 xcframework folders that don’t start with “lib” (they are static frameworks)

Add the Keychain Group Intune MAM

Then In Signing & Capabilities tab, go in Keychain Sharing section and add:

com.microsoft.intune.mam
Add the keychain group “com.microsoft.intune.mam”

You can notice that I also have added com.microsoft.adalcache that is related to MSAL (Microsoft Authentication Library).

Add headers to your AppDelegate.mm and AppDelegate.h

AppDelegate.h

#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <IntuneMAMSwift/IntuneMAM.h> // <--- Add it

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, IntuneMAMComplianceDelegate> // <--- Add IntuneMAMComplianceDelegate

@property (nonatomic, strong) UIWindow *window;

@end

AppDelegate.mm

#import <MSAL/MSAL.h>
#import <IntuneMAMSwift/IntuneMAM.h>
#import <IntuneMAMSwift/IntuneMAMEnrollmentManager.h>
#import <IntuneMAMSwift/IntuneMAMComplianceManager.h>
#import <IntuneMAMSwift/IntuneMAMFileProtectionManager.h>
#import <IntuneMAMSwift/IntuneMAMDataProtectionManager.h>
#import <IntuneMAMSwift/IntuneMAMAppConfigManager.h>
...
// isEnrolledAccount : method to know if microsoft account is enrolled
RCT_EXPORT_METHOD(isEnrolledAccount:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSString *userIdentity = 'your_name@your_company.com';
    IntuneMAMEnrollmentManager *enrollmentManager = [IntuneMAMEnrollmentManager instance];
    
    BOOL isIntuneEnrolled = (enrollmentManager.enrolledAccount != nil);
    BOOL isMDMEnrolled = (enrollmentManager.mdmEnrolledAccount != nil);
    NSLog(@"Is Intune Enrolled: %@", isIntuneEnrolled ? @"YES" : @"NO");
    NSLog(@"Is MDM Enrolled: %@", isMDMEnrolled ? @"YES" : @"NO");

    if (isIntuneEnrolled) {
        resolve(@"Intune");
    } else if (isMDMEnrolled) {
        resolve(@"MDM");
    } else {
        resolve(@"None");
    }
}

And you can have a look on the available functions for the library in the header files. Here are all the headers you can use in your code:

And if you go on each headers you can see that the documentation is very well explained for each API available, here is an example with IntuneMAMComplianceManager.h

Each functions have comments that explain how it works, you will have the read all depending of your needs

Hope that this article helps ! Don’t hesitate to rate & comment the article.

React Native : fix ‘React/RCTDefines.h’ file not found

It is possible that after adding a new library in your React Native project (or after upgrading or adding some packages) you have the following error in XCode when you try to build:

<React/RCTDefines.h> file not found

or

'React/RCTDefines.h' file not found

This error means that the library is not correctly set up with your React Native project.
To fix this error, go to your library settings. You will have to modify the Header Search Path input in Build Settings :

Go to Libraries > YourLib.xcodeproj > Targets > Build Settings > Search “Header Search Path”

“Header Search Path” should be in the section “Search Paths”. I recommend to use the search input because there are a lot of fields.

In my case, the external library named “RNReactNativeMstuneMam.xcodeproj” was doing the problem, so I am opening the settings of the xcodeproj file of this project. It can be any other library with another name, but you will have to fix the xcodeproj file, following to the error path that you have.

Now, double click to Header Search Paths and add:

${SRCROOT}/../../../ios/Pods/Headers
Add the entry ${SRCROOT}/../../../ios/Pods/Headers by double clicking

Set it to recursive:

set it to recursive

Now, you can clean and build again:

Clean project and build again

The error should not appear anymore as your library will find the Pods headers.

Block specific exact content with Adblock Plus : Youtube, LinkedIn, … stupid suggested content

As you can know we (software developers) are facing serious information overdose everyday, especially on social networks and video services such as Youtube.

On Youtube, like on Twitter, etc…. , you can both found shitposts and useful informations. This is a serious problem because we can easily waste time by filtering ourself everyday theses informations which take energy and time. This is a real problem.

Youtube : the grid of stupid videos

Exemple of the Youtube main page with useless content

We can block the main Youtube page with add-ons like Adblock Plus (using others are possible but I will use this one in this tutorial).
As you can know, the social networks have psychologists and they are working hard with complex algorithms to waste your time and so you can be more on their platform and generate more money mainly with advertisement.
You can still see it if it is not a problem for you. As for me, the content is not relevant at all because watching cat videos will not help me about business, software development, earning money, and so on.

The solution

On Adblock Plus we can go to the Advanced settings :

You can add custom filters in this list

In “My Filter List” you can add filters that can block only the part of the website that contains useless informations.

Here is the Adblock Plus Cheatlist : https://adblockplus.org/filter-cheatsheet

We can now add the following rules to block the contents DIV

The DIV with “content” id is showing the useless content

So, we can try to add the following filter:

youtube.com###contents

The problem if you use the rule is that you will not be able to a Youtube search:

The search “earn money” was also blocked

Now, if we go deeper in inspecting the DOM, we can see that:

  • On the main page the DIV contents contains the class ytd-rich-grid-renderer if it is the main page
  • On the search page the DIV contents contains the class ytd-section-list-renderer (and others)

If you look on the documentation of Adblock Plus, on the cheatlist, you can see that you can also block the ID and the CLASS, which will give the following rule:

youtube.com###contents > .ytd-rich-grid-renderer

The result:

The main page, blocked ✅
The search page, not blocked ✅

LinkedIn : the infinite scrolling

linkedin.com##.scaffold-finite-scroll

If you have others you can tell me ! 😃

Thank you for reading my article, do not hesitate to support me by contacting me or making a donation.

List of the array method with Javascript

.forEach()

const array = ["Alpha", "Beta", "Omega", "Neptune", "Danube", "Dniepr"];
array.forEach(el => console.log(el)) // basic
array.forEach((el, index, array) => console.log(el, index, array)) // all params
array.forEach(function(el, index, array) { // without arrow function
   return console.log(el, index, array)
})

.map()

const users = [
{name: "Alpha", level: 1}, 
{name: "Beta", level: 2}, 
{name: "Omega", level: 3}];
const names = users.map(user => user.name) // basic mapping in new array
console.log(names) // ["Alpha", "Beta", "Omega"]

.find() / .findIndex() / .indexOf()

const array = ["Alpha", "Beta", "Omega", "Neptune", "Danube", "Dniepr"];
console.log(array.find(el => el === "Alpha")) // ["Alpha"]
console.log(array.findIndex(el => el === "Alpha")) // 0
console.log(array.indexOf("Neptune")) // 3

The difference between indexOf and findIndex :
– findIndex : callback function so can do more things inside the callback function
– indexOf : using fast function with only one parameter

const users = [
{name: "Alpha", level: 1}, 
{name: "Beta", level: 2}, 
{name: "Omega", level: 3}];
console.log(users.find(el => el.name === "Alpha")) // [{name: "Alpha", level: 1}]

.some() / .every() / .includes()

const numbers = [12,14,23,27,11,9];
console.log(numbers.some(num => num === 40)) // false - do not have it
console.log(numbers.some(num => num === 12)) // true - yes have it
console.log(numbers.every(num => num < 20)) // false - not every item bellow 20
console.log(numbers.every(num => num < 40)) // true - yes every item bellow 40
console.log(numbers.includes(40)) // false - do not have it
console.log(numbers.includes(12)) // true - yes have it

.filter()

const numbers = [12,14,23,27,11,9];
const filteredNumbers = numbers.filter(num => num > 20) // [23, 27]

.sort()

Sort() works pretty good to order by ascending alphabetical order.
For numbers you will need to overload the condition in the callback function.

const numbers = [12,14,23,27,11,9];
const sortedNumbers = numbers.sort((a, b) => a - b) // [9, 11, 12, 14, 23, 27]
const array = ["Delta", "Neptune", "Alpha", "Danube", "Beta", "Dniepr"];
const sortedZone = array.sort() // ['Alpha', 'Beta', 'Danube', 'Delta', 'Dniepr', 'Neptune']

.reduce()

const numbers = [12,14,23,27,11,9];
const reducedNumbers = numbers.reduce((accumulator, currentValue) => accumulator + currentValue) // 96

Liste des plateformes de freelance en tant que développeur fullstack en France

Trouver des missions en freelance peut ne pas être toujours très simple mais en étant sur les bons canaux de communications et de diffusion d’offre, il est possible de pouvoir avoir de bonnes opportunités :

Lien: http://malt.fr

Lien: http://comet.co

Lien: https://fr.capgemini.talentnet.community/

Lien: http://freelancerepublik.com

Lien: https://www.freelance-dispo.fr/

Lien: http://free-work.com

Lien: https://www.espace-freelance.fr/

Lien: https://www.littlebigconnection.com/

Ensuite, les autres plateformes classiques peuvent offrir des opportunités en freelance : LinkedIn Jobs, Indeed, etc.

Ruby on MacBook M1 : Not updating version

In some cases, it seems that your ruby version can still on 2.6 (which is old, latest is 3.2.2 right now) even if you installed the lasted version with brew or rbenv. You can see also that you have rights problems and need sudo rights to install some gems.

# ruby -v
ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin22]
# rbenv install 3.2.2
# rbenv global 3.2.2
# ruby -v # WE CAN SEE THAT WE STILL HAVE OLD VERSION
ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin22]

This is because MacOS comes with a “system Ruby” pre-installed. If we remove all we can see the pre-installed version is located in this folder:

$ which ruby
/usr/bin/ruby

What should we do to not use system Ruby

There are two methods that I recommend, the method using brew or the method using rbenv which both allows you to manage Ruby version.

Method 1 : Updating Ruby version with Brew

First you need to install the Ruby version with brew:

brew install ruby ruby-build

Then open a terminal and go on your personal folder (/Users/YOUR_USERNAME) and use the following command to switch from the Ruby system version to the brew version with the .zshrc file in your user folder:

echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc

Method 2 : Updating Ruby version with RBEnv

If you still see the old version by checking the version doing ruby -v
First you need to install rbenv, which is a ruby version manager, we can install it with brew.

brew install rbenv ruby-build

Then open a terminal and go on your personal folder (/Users/YOUR_USERNAME) and use the following command to switch from the Ruby system version to the rbenv version with the .zshrc file in your user folder:

echo 'eval "$(rbenv init -)"' >> ~/.zshrc

Check the result

Then, open a new terminal (close the old one), if you updated correctly the zshrc file and have installed the correct ruby version, you should be able to see the good version:

% ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]

Hope it will work for you. Do not hesitate if any question.

Playwright: Count all the components on the page

Is it possible that you have to debug Playwright and list all the available items on the page, so you can know if your locate your item const buttons = await page.$$(‘button’); or your click await buttonButton1.click() will work. To do so you can list all items and also highlight them when you run npx playwright test , add the following in your playwright test file, in any test:

  const componentHandles = await page.evaluateHandle(() => {
    const components = new Map();
    const walker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_ELEMENT,
    );

    while (walker.nextNode()) {
      const element = walker.currentNode as HTMLElement;
      const nodeName = element.nodeName.toLowerCase();
      if (components.has(nodeName)) {
        components.set(nodeName, components.get(nodeName) + 1);
      } else {
        components.set(nodeName, 1);
      }
      element.style.border = '2px solid red'; // Highlight the component
    }

    return Array.from(components);
  });

  const componentCount = await componentHandles.jsonValue();

  console.log('Component count:');
  for (const [component, count] of Object.entries(componentCount)) {
    console.log(`${component}: ${count}`);
  }

Result:

0: noscript,1
1: script,1
2: div,41
3: input,3
4: span,3
5: aside,1
6: style,1
7: button,2
8: svg,1
9: g,2
10: path,3

And it will display on the web browser you item with red borders.

Using ReactQuery with React instead of Axios + hooks

In your React app you can use ReactQuery and ReactQueries for request caching, background updates and stale data out of the box with zero-configuration, instead of just Axios + hooks.

UseQuery : 1 api request

import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
...
const getServices = () => {
        return axios.get('https://jsonplaceholder.typicode.com/posts');
};
...
const { data, isError, isLoading } = useQuery(['services'], getServices);

if (isLoading) {
    return <p>Loading...</p>;
}

if (isError) {
    console.error('error');
}

return <ServiceComponent data={data} />;

UseQueries : 2+ api request

Index.tsx

import { useQueries } from '@tanstack/react-query';
import axios from 'axios';
...
const getServiceOne = () => {
        return axios.get('https://jsonplaceholder.typicode.com/posts');
};
...
const results: any = useQueries({
    queries: [
      { queryKey: ['serviceOne'], queryFn: getServiceOne, staleTime: Infinity },
      { queryKey: ['serviceTwo'], queryFn: getServiceTwo, staleTime: Infinity },
    ],
});

const serviceOneResult = results[0];
const serviceTwoResult = results[1];

  const isError = serviceOneResult.error || serviceTwoResult.error;
  const isLoading = serviceOneResult.isLoading || serviceTwoResult.isLoading;

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (isError) {
    console.error('error');
  }

  return (
    <ServiceComponent
      dataOne={serviceOneResult.data}
      dataTwo={serviceTwoResult.data}
    />
  );
моя жизнь - дерьмо