Grunt-ing discomfort

Lego gruntI recently gave grunt an extensive test run, on a number of (relatively varied) WebItUp and Roxee projects, in order to evaluate its use for our clients and internal projects alike.

Indeed, grunt is appealing, at least for “web” projects (and most projects are “web” nowadays, for the better or worse), at least more appealing than rake/make/poisonake – web projects that ought to perform the same well identified build operations over and over again (hint-ing, test-ing, karma-ing, uglify-ing, you-name-it-ing).

Continue reading

Notes about fiddling with docker and continuous deployment ideas

If you don’t know what docker is yet, you may read this.

In a word, docker is to linux containers what the meat and beer is to the idea of barbecue (sure, you are free to try without, but, hey, why not try ferret legging while you are at it? :-)).

Linux containers provide a lightweight virtualization mechanism. While you do have less isolation than with traditional virtualization technics, you consume a lot less resources and can “boot” way faster.

Then Docker provides tools to manipulate and setup containers easily, and to “describe” requirements and software deployment operations.

So what?

By describing your software installation requirements and setup operations in a simple “Dockerfile” and by building ready-to-use “complete images” that you will run as a linux container you are:

  • building a fully described, consistent (dev, preprod, prod), stable deployment environment for your application
  • simplifying production deployment (simply boot a container from your image and NAT it)
  • make versions management/test and rollback easier (change NAT from one container to another)
  • easing tests on multiple versions (simply run your tests against a specific container)

… hence, if you couple this with a CI tool (we are doing tests with strider CD here) you are one step closer to the mythical “continuous deployment” Graal (write code > commit > autorun tests > auto deploy).

Kick the shit!

Let say you have a box using some naked Ubuntu LTS 12.04.

Well, take a quick look at this (that can’t hurt), or otherwise do what you have to do to secure it.

Now, you need docker:

# Add the Docker repository key to your local keychain
# using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9
sh -c "curl https://get.docker.io/gpg | apt-key add -"
# Add the Docker repository to your apt sources list.
sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
# Update your sources
apt-get update
# You need at least Linux 3.8, so, let's do that
apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring

Reboot, and check that you are indeed using linux >= 3.8.

# Install, you will see another warning that the package cannot be authenticated. Confirm install.
apt-get install lxc-docker
# Create a new user with docker rights
useradd webitupbot
mkdir -p /home/webitupbot
chown webitupbot:webitupbot /home/webitupbot
usermod -a -G docker webitupbot

Now, your new user can use docker as he sees fit. You can already try fiddling with docker commands (see the tutorial).

The private registry

Building images is cool, but you likely want to “push” them somewhere when the build succeeds. Docker provides a registry server application for that, against which you will be able to “push” and “pull” your images.

Let’s install it and run it from its own docker image (eat your own dog food, people!):

# You do have supervisord already, right?
apt-get install supervisor

Create a new /etc/supervisor/conf.d/supervisord-docker-registry.conf file:

 

[program:registry]
command = docker run -d -p 15000:5000 samalba/docker-registry
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
user = webitupbot
autorestart=true

Restart supervisor if needed. That’s it. Cool hey?

Now, check that the service is running:

docker ps should output something like:

ID IMAGE COMMAND CREATED STATUS PORTS
b59896b24b09 samalba/docker-registry:latest /bin/sh -c cd /docke About an hour ago Up About an hour 15000->5000

And you should be able to reach it with curl:

curl -iv -o/dev/null localhost:15000

If something is wrong, check the supervisor logs:

tail -f /var/log/supervisor/*

If you can’t figure out what’s wrong, try to launch it from the command line instead of using supervisord, and get it up.

Now, we probably want the registry to run on a nice port (80/443), behind a nice proxy. So, let’s have some SSL nginx in front of it.

apt-get install python-software-properties
nginx=stable # use nginx=development for latest development version
add-apt-repository ppa:nginx/$nginx
apt-get update
apt-get upgrade
apt-get install nginx

Now, configure it:

upstream docker {
 server 127.0.0.1:15000;
}
server {
 listen 443 ssl;
 client_max_body_size 800M;
 server_name XXXXXXX;
 access_log /var/log/nginx/access.docker-snack.log;
 error_log /var/log/nginx/error.docker-snack.log;
 ssl_certificate /root/ssl-certs/XXXX.crt;
 ssl_certificate_key /root/ssl-certs/XXX.key;
location / {
 proxy_pass http://docker;
 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
 proxy_redirect off;
 proxy_buffering off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto ssl;
 }
}

Restart nginx and check that you can reach your registry on YOURDOMAIN:443.

Now, you can build images pointing to your registry:

docker build -rm -t YOURDOMAIN:443/myname/myimage

… then push them to your registry:

docker push YOURDOMAIN:443/myname/myimage

… then pull them from another host:

docker pull YOURDOMAIN:443/myname/myimage

See the gotchas if you are having troubles.

Dockerfiles for the mass

You probably want to start by having some “generic” images at hand, that you will then use as a basis for your projects.

I like to split things, so, my basic images start with “baremetal” – here is the Dockerfile:

# Yes, we are running LTS
from ubuntu:12.04
# Remi is in charge
maintainer Remi Olive <remi@webitup.fr>
# Opening-up the repo to universe as well
run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
# Check that we are up to date
run apt-get update
run apt-get -y upgrade
# Keep upstart from complaining
run dpkg-divert --local --rename --add /sbin/initctl
run ln -s /bin/true /sbin/initctl
# Useful base packages
run apt-get -y install supervisor
run mkdir -p /var/log/supervisor
run locale-gen en_US en_US.UTF-8
add supervisord-bare.conf /etc/supervisor/conf.d/
# Change root password
run echo 'root:XXXXXXX' | chpasswd
cmd ["/usr/bin/supervisord", "-n"]

Now, put whatever basic setup you want in the supervisor file, or comment out the add statement.

You should build that and push it:

docker build -rm -t YOURDOMAIN:443/base/baremetal . && docker push YOURDOMAIN:443/base/baremetal

Now, a second image inherits that and adds ssh:

# From base
from YOURDOMAIN:443/base/baremetal:latest
# Remi is in charge
maintainer Remi Olive <remi@webitup.fr>
# SSH base
run apt-get -y install openssh-server
# Setup
run mkdir -p /var/run/sshd
# Prepare
run mkdir -p /root/.ssh && chmod og-rwx /root/.ssh
# Secure configuration - disable password authentication
RUN sed -i -e "s/#PasswordAuthentication yes/PasswordAuthentication no/g" /etc/ssh/sshd_config
# Add files
add supervisord-ssh.conf /etc/supervisor/conf.d/
add authorized_keys /root/.ssh/
cmd ["/usr/bin/supervisord", "-n"]
expose 22

You obviously want to put your public key in authorized_keys, and your supervisord-ssh file might look like:

[program:sshd]
command=/usr/sbin/sshd -D
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
autorestart=true

Now, what about something with nginx?

# From base
from YOURDOMAIN:443/base/ssh:latest
# Remi is in charge
maintainer Remi Olive <remi@webitup.fr>
# Nginx at last
run apt-get -y install nginx
# As to avoid making supervisor miss the train
run echo "daemon off;" >> /etc/nginx/nginx.conf
add supervisord-nginx.conf /etc/supervisor/conf.d/
cmd ["/usr/bin/supervisord", "-n"]
expose 80

Again, your supervisor file might simply be:

[program:nginx]
command=/usr/sbin/nginx
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
autorestart=true

Now, build all three of these, push them, and forget about it.

Assuming you have a nice project that generate some html/javascript/css in a “dist” folder, you can add a very simple Dockerfile like that to it:

from YOURDOMAIN:443/base/nginx
maintainer Mangled Deutz <dev@webitup.fr>
add ./pukes/nginx.conf /etc/nginx/sites-available/default
add ./dist /var/www
cmd ["/usr/bin/supervisord", "-n"]

Be sure to provide an nginx.conf file for your domain that fit your needs.

Now build that with docker and start it.

You have a standalone, self-contained web server for your project.

The last step is to run the container and proxy it through your host nginx server.

As I have many projects like that, I want them to be all available on a url like: https://integration-server/owner/repo

The relevant part of the corresponding nginx configuration would go something like:

server {
location ~ ^/(?P<back1>([^/]+))/(?P<back2>([^/]+)) {
 rewrite ^/[^/]+/[^/]+(.*) /$1 break;
 proxy_pass http://$back1.$back2;
 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
 proxy_redirect off;
 proxy_buffering off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 }
}

And each “project” would have to deploy a snippet that would define the backend, and reload nginx configuration:

upstream owner.project {
 server CONTAINER_IP:CONTAINER_NATTED_PORT;
}

Obviously, network details about a running container are obtained using docker inspect.

Note that Hipache might be a better choice than this hackish nginx setup.

Hooking github

Using Strider, or any other solutions, you may use webhooks to trigger a rebuild each time you commit – then if the container does build-up, run some automated test (we are using Karma, Browserstack / Sauce and jasmine), and depending on the result, push the image, and possibly ping another service that will deploy the new version in production. Failing containers are still useful to keep for inspection obviously.

GOTCHA!

What could possibly go wrong?

A lot!

You absolutely need a recent nginx to have the registry work (>= 1.4). Provided instructions should be followed to the letter. Most weird errors with docker push/pull are linked somehow to the (previous) use of an old nginx.

If you are gonna use SSL, you need a valid certificate. Specifically, if you are using Gandi, you need to append Gandi pem in the chain. Docker client seems to be very picky about SSL validation errors, and I couldn’t find a way to bypass SSL errors. Repeat again: you need a valid SSL certificate if you want SSL. In order to debug your SSL setup, you may use openssl (openssl s_client -connect mydomain:443) or some clicky-click tool like http://www.digicert.com/help/

Docker won’t let you use ADD directive with stdin provided dockerfiles (see https://github.com/dotcloud/docker/issues/841) – this might bite – as painful as it is, you may end-up using temporary Dockerfiles just for the sake of having a context. Be aware that docker WON’T tell you what’s going on (eg: ADD directive being unusable with stdin dockerfile) but instead will barf about your added file being non-existent.

Docker might freak out on you and complain about being ”Unable to mount using aufs”. This might very well be due to a limitation in AUFS (are you using a lot of run statements? or basing on an image that does?). See https://github.com/dotcloud/docker/issues/1006 and https://github.com/dotcloud/docker/issues/1171. Although Docker encourages you to stack layers (see http://docs.docker.io/en/latest/use/builder/#run) it might be a better idea to group your commands in a script and run that script instead.

Docker might simply stop being usable – your containers loosing the network entirely. The symptoms are docker daemon failing to setup iptables at start (see dmesg), and network being unreachable from within your containers. See here: https://github.com/dotcloud/docker/issues/866 if you like to read. If you just want it back and working, go with:

pkill docker
iptables -t nat -F
ifconfig docker0 down
brctl delbr docker0
docker -d

There is no way that I’m aware of that would let you include “recyclable snippets sections” (some “include” directive) in your dockerfiles (that would come in handy in order to pick and match certain components). You can certainly do that with any build system on top of dockerfiles that would generate them (or some nasty shell scripting).

Finally, I ran into numerous other docker bugs while fiddling with it – specifically:

https://github.com/dotcloud/docker-registry/issues/47 – solution: trash the registry container and boot a fresh one (obviously you need to republish your images)

multiple images with the same id (wtf?), and failure to rmi them – ended-up trashing the docker images folder manually.

All in all, there is a lot of good in docker – and it’s clearly a huge step forward in simplifying application deployment and testing. The downsides right now are mainly the still sketchy documentation, and the (somewhat numerous) bugs that might get in the way.

Air France, Alitalia, the Crap Team

I never, ever, bitch on the internet. This is an exception.

I am (or shall I say I was?) a regular Air France customer.

Sure, I’m not flying every week.

But still I know my wings a bit – that past year, twice to NYC, once SF, two times Lisbon, three times Tolosa, every time with my business associate, or the girlfriend.

We always fly Air France – partly because I’m lazy and I don’t want to change my routines for the sake of change, partly because I’m a very faithful customer: as long as you make me reasonably happy, I’m not going to leave, even if the price is cheaper elsewhere (you know what you leave, never what you gonna get). That’s who I am.

I rarely take vacations.

Workaholic, that’s who I am.

But the girlfriend doesn’t dig that, and I have to comply to her demands, sometimes…

So, a week in Sicilia was set.

Departure Tuesday, 9AM.

First leg to Milan operated by Air France, second leg to Palermo, operated by Australian Air Express.

Never heard of them, but well, I bought the tickets from Air France didn’t I? And I trust them.

 

A couple of weeks before the flight, departure time was changed.

No explanation, no reason for that, but well, if Air France requested that… I trust them, don’t I?

 

Once at the airport, the plane was announced to be 15 minutes late.

No big deal, right?

With a (short) one hour transit time between the two planes, 15 minutes late still makes for a whopping 45 minutes time window.

Now, every time the delay was going to expire, five more minutes were being added.

So the 15 minutes turned 40.

Still, no big deal, right?

 

Arriving at Milan, 40 minutes late, and knowing the boarding technically started already on the second flight, I asked the stewart if my connection was still possible.

“You have to run” he said.

Well, sure I can run.

I was rather expecting he would call the boarding gate and ask them to wait for us.

Or better, I was rather expecting he (or someone else) would have known I was (and other passengers as well) connecting, and would have called without me asking.

But no, I had to run.

I also asked if my luggages would be following me ok, with that short connection time.

“Absolutely” he said.

So, I ran – and so did the courageous girlfriend on high heels.

No big deal, right?

 

We made it to the second plane.

To the gate actually.

Surprise, that was no longer Australian Air Express, but Alitalia.

Well, no big deal, right?

The attendee took my boarding pass, and started tearing it in pieces.

I surely look amazed, but he handed me back two pieces out of three, one with the flight detail and seat number, the other with my luggage sticker.

Not too sure why he did that – maybe because he obviously didn’t have a working computer to record passengers?

Amazing, but no big deal.

I asked him (again) if my luggages were going to do ok (as passengers have no mean to verify that on their own, do they?).

“Sure”, he said, “why wouldn’t they?”.

 

Arriving in Palermo, my luggages weren’t there.

Neither the girlfriend luggage.

Neither another french couple luggages, from the same Air France plane.

A systemic problem, obviously, not just some one time snafoo (that will be confirmed by about everyone I would talk to about that later on).

 

Well I am a very patient person. So, no big deal. There is a plane every hour (or two) from Milan, surely they will come with the next one.

 

I went to the missing luggage booth, to declare the loss.

The attendee recorded that, and told me my luggages were certainly going to be there on the next plane from Milan.

So I waited in the airport (crappy airport, Palermo – among the crappiest I ever landed).

The next plane arrived, but my luggage weren’t there.

The attendee again swore me they were gonna be on the next one.

So, I waited again.

Again, my luggages weren’t there.

So I asked the attendee if he could get any useful, accurate information about my luggages.

He simulated typing something on a computer (I say simulated – because I am not a fucking moron, and I am fucking working computers all day), and told me they were likely coming with one of the next planes that day.

And that “they will delivered to you at your address”.

“In 48 hours”, he added.

Right.

48 hours delivery from point A (the airport), to point B (an hotel), 50 miles away.

Right.

Using asmatic donkeys?

I told him, as nicely as I could, that such conditions were not good enough, and that I was willing to stay a couple hours more in that airport if he could guarantee me my luggages were coming with one of the next flights.

Obviously he couldn’t, but that was a very gentle smile, very meaningful.

And, very nicely, he said me that he would escalate his hierarchy in order to obtain for me a “priority delivery, that is a 6 hour delivery instead of 48″, and that I really should call the number written on the lost luggage receipt in order to obtain an ETA for the arrival in Palermo (and that “they” would call me anyhow on my cell as soon as any news would come in – which they will never do – not a single text message).

 

Right.

 

So I called the phone number.

Two, three times.

No one to be reached.

The fourth time it went through:

“Somebody: Pronto”

“Me: Buôn jorno. Do you speak english?”

“Somebody: …….”

“Me: Ok. Vous parlez français? Habla espagnol? Sprechen sie deutsch? Speakish danish?”

“Somebody: parlano francese appellare francia” (this is what I understood at least)

 

then hang-up.

 

 

So I went to the Avis-rent-a-car booth next to the airport (because I have a thing with rent-a-car-booth-girls).

I made two pleasant jokes and a nice smile to the girl there, explained my situation, asked her if she could help… and she called!

But nobody answered.

 

I tried my luck again with another nice-face at the airport information booth.

She tried calling twice. Then a third time, and it went through!

 

No need for her to translate. I saw her face.

Our luggages were going to be delivered, at the (unknown) time when they were gonna be delivered, “they” would call me anyway to give an ETA, and then it would take 48 hours to get them to our location.

 

 

No big deal, right?

I’m a computer scientist, right? I can definitely live an entire week out of a slice of pizza and the same underwear, right?

Yeah sure – but what about living without your macbook power plug?

What about surviving a week with the (already moody) girlfriend, whose only vacation this year is now, and who is not a computer scientist, who needs shoes, dress, proper and clean underwear, medications, a hairdryer and other stuff I don’t know the name for but that definitely weight a ton and seem expensive.

Kind of lucky I brought my two MacBooks.

 

So we went to Palermo before reaching the hotel, and we started buying a survival kit.

That was about 400 euros, for the bare minimum, for three days. Yes, that’s the cost of things. Crappy stuff still.

 

 

We finally landed at the hotel, six hours late.

I already had asked the business associate (who stayed in France to keep the sheep flocking), to dig me a complaint phone number at Air France.

 

That was 48 hours ago.

 

Just an hour ago, having exhausted all other possibilities, I rang that number (knowing that such a phone call from here would cost me an arm, thanks to Bouygues Telecom – but I’m a faithful customer, remember?).

Someone from Air France answered me.

I explained the situation, as gently as possible.

He answered me: “That’s not our problem. The international convention clearly explains the last leg company is responsible for the luggage. You have to call Alitalia”.

So much for the Sky Team spirit, right?

To what I answered: “I bought a ticket from Air France. I didn’t choose Air France providers, or which company would operate the last leg flight. When you rent an apartment, the lodger is responsible for the plumbery.”

He couldn’t answer that, obviouly.

I then explained to him that:

  • I don’t speak italian
  • I have almost no battery left in either my phone or (first) MacBook, and no mean to search internet over a 3G connection that costs insanely
  • I have no idea how to find someone at Alitalia that would accept to speak english, and then who would answer any question without lying tremendously through their fucking teeths
  • I don’t give a fuck about what lame excuse Air France could come up with to NOT help me with this, and that this is my crappiest experience with them (or any flight company for that matter) in years
  • this situation was not his fault personally, and I have no hard feelings against him, but the least the company could do was helping me obtain an ETA about the situation (at that point I don’t think I’m going to get my luggages back anytime soon)

 

After a couple minutes, the man finally gave me a (french) number, supposedly Alitalia.

 

I tried calling it about ten times then, and either got disconnected, or ended-up speaking to an answering machine that doesn’t even mention who the fuck this number belongs to.

 

Here I am.

Thursday, 6PM.

 

I have less than one hour autonomy left on the MacBook air (the pro is dead already), and a full iPhone battery.

What I think is notable:

  • Air France, although being the company from which I bought my travel, refuses strictly to endorse any responsibility, or even to contribute any form of help in fixing the situation
  • Air France chosen partners and contractors are seemingly irresponsible in the way they handle things, are unable or unwilling to give any practical information about what’s happening, and are either brutally rude or simply plain lying
  • I still haven’t got my luggage back, after two days, which as I said is no big deal for me, but which definitely is a big deal for the girlfriend who NEEDS HER MEDICATION that I had to literally steal from the local pharmacy (yes, this is controlled, prescription-only stuff)
  • I think a company that refuses to help its customers by arguing that its sub-contractors are responsible is simply shooting itself in the foot

 

At that point, I don’t even want to think about how I will get compensation for the stuff we had to buy, but given the story so far, I don’t think I am going to stay gentle and polite any longer.

Finally, unless Air France would suddenly change behavior, and start either:

  • demonstrating any will to help obtaining any information about the status of things
  • actually do something to fix it
  • express any form of apology
  • compensate me quickly

They would definitely lost three (very) patient and faithful customer (the girlfriend, the business associate, and me).

About Mozilla mobile marketing speech

I started writing this as a comment on a recent Beyond the Code blog post – though what I would like to say sometimes goes OT to the original post, and… Posting my comment (twice) ended-up with a blank page, pending POST, and 405 on the subsequent GET. Maybe somebody tripped on the lizard tail there and disabled comments, in a truly open-web fashion? :-)

Either way… This has been bugging me for quite some time, and I need to get it out of my system in order to get back to useful stuff.

Continue reading

What not to do to write portable javascript for (former-) mozilla-addicts

Firefox’s javascript engine has a number of non-standard core extensions / features (just like some other browser). There seem to be a number of people who have grown used to these in the past (doing too much xul is bad for your health, you know?), and there also are a number of newbies who learn javascript using mdn, not necessarily paying attention to what is standard, deprecated, obsolete, and what not.

Having to refactor such codebases, written under the influence, may require a cheat sheet to quickly spot firefoxisms, and there doesn’t seem to be a good list out there.

So, let’s try establishing that list. If something is not there, add a comment on that post.

The purpose is not to list what might work, and where – just what is not standard among Firefox features (and using non-standard features is pretty bad, right?). In other words, what should just be avoided / removed, in a web context aiming at being cross-browser.

Non-standard for Strings

Non-standard stuff for Objects

Non-standard for Functions

Javascript AMD considered harmful: autopsy of a persistent discomfort

AMD? The hardware company?

The Asynchronous Module Definition pattern (or AMD for short – possibly one of the most clumsily nicked concept in computing history) is an attempt at “solving” javascript lack of a good, native, code modularization and dependency resolving solution.

Indeed, while javascript lets you organize your code in a fairly intuitive and malleable way (say: MyLibrary.someNamespace.someModule), there is no (native) way to express (nor “fetch”) dependencies, which leaves javascript developers with unsolved problems:

  • refactoring code is a dangerous task
  • large codebases have unclear dependencies
  • code tend to quickly become unmaintainable and unclear if not written very carefully
  • lazy-loading of additional code is not trivial to do right

Continue reading

Cocoa: get user preference “minimize on title bar double click” to implement behavior in borderless windows

Apps that want a custom title bar on OSX usually have a NSBorderlessWindowMask on the NSWindow, with the drawbacks of:

  1. Not being able to switch workspace by reaching the edge of the screen
  2. Not being able to minimize to dock on double click (if the user activated the setting in the pref panel)

But it’s possible to fix #2.

The trick is pretty much how to access the user preference:


NSString *const MDAppleMiniaturizeOnDoubleClickKey = @"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults addSuiteNamed:NSGlobalDomain];
bool shouldMinimize = [[userDefaults objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];

And voilà. The rest is piece of cake: catch double click on your custom header, check the value of shouldMinimize, then call minimize on your window.

We implemented it successfully in Roxee (QT).

Credit goes to user NSGod on stackoverflow.

Ha! Rdio, you failed too… at switching workspace on OSX

Applications on Mac that wish to have a “custom” title bar can go two roads.
The easiest IMO (and the one that allow seamless integration with the app body itself) is to go Qt::FramelessWindowHint (or NSBorderlessWindowMask).

This is what we do – and I’m pretty sure this is what Rdio does as well.

Usually, that means you have to implement moving yourself (and probably resizing as well).

There is one caveat: there is no Cocoa programmatic way (AFAIK) to make the app switch workspace (this is usually triggered when moving an app to the current space edge, and waiting a bit).

So, our Roxee client fails here…
Spotify works ok (but I’m pretty sure they don’t do NSBorderlessWindowMask, but rather have a custom title bar).
And Rdio fails, just like us.

At least, we are not alone.
And I would be glad to hear about any solution to this that doesn’t involve messing with private Cocoa API…

[UPDATE]

Rdio guys know about this

Facebook killed me

Wow, tricky bug … Let’s say you have a Facebook application in Sandbox mode. You are working as a connected Facebook user. You’re using FB.getLoginStatus() and everything is working fine.

Now you want to test your code with an unauthorized user. It works. With unlogged user, your callback is not fired, blackout, KABOOM !!!

 

You’re are NOT doing it wrong ! This a bug

This bug is well known by their developers but flagged as “By design”. In other words “won’t fix” !

Here is my workaround.

Continue reading