Professional Scrum Product Owner I

PSO

Certified by Scrum.org

How to change String in swf without decompiling

Sometimes I get a projects, that people that request them do not have the source files. Sometimes they need quite much of the functionality change, sometimes they just need hardcoded links to be update or something small.

To update string in already compiled swf is quite easy. You need first a tool called: JPEXS Free Flash Decompiler

Then is simple you open the swf, and search for the link:

search_for_the_string

Then you need to edit the link and then save the change afterwards:

edit_it

Then you save the swf

save_after_change

User Stories

A simple user story is:

As a user open the mobile application, I want to be able to download videos on phone, so later he can watch them offline.

As this works for the non-tech people to let them know of functionality, for the developers usually have some problems:

  • it is hard to be estimated;
  • do not say what to happen if errors occur – user internet stop in middle, error of missing video for download and else;
  • do not cover what/ where / how;
  • how should be tested?

So I usually do it like (with the help of the developer/ team if needed):

User Story:

title:

Downloading Video

Description:
As a user open the mobile application, I want to be able to download videos on phone, so later he can watch them offline.

Acceptance Criteria:

  • show user error when video is not able to be downloaded;
  • pause the download when user go offline, and resume when is online;
  • notify user by notification that video is downloaded on the device;
  • show ETA (estimated time of download);
  • show video downloading status: (Start Download / Downloading (23 sec left) / Downloaded!);

// or Test Cases:

Test Cast 1:

Check video downloading on internet connection lost

Test Steps:

  1. Open application;
  2. Click on video menu;
  3. Click on video button “Download” to downloading video;
  4. Stop Internet;
  5. Start internet gain.

Expected Result:

video should resume download.

Test Case 2:

Start download video that result in missing video link (404 error).
Test Steps:

1. Open application;
2. Click on video menu;
3. Click on video button “Download” to downloading video;

Expected Result:

Should notify the user that the video is not available at the moment.

// this is usually the same for all the items;
Definition of Ready:

  • passed all acceptance criteria items;
  • able to show the feature in on mobile phone – iOS / Android
  • passed all test of QA;

Definition of Done:

  • approved after ready by the PO;

Other memos:

  • mockups / wire-framing;
  • design;
  • customer comments / notes;
  • else….

Securing SWF code to not be visible

Adobe Flash Players operates in the Presentation layer (of Multitier architecture ). As Server-side scripts and database are located on the host provider and cannot be accessed in their raw nature, the swf are downloaded via browser and then shown to the user. This lead to option to very easy see the flash player code. You just install Sothink SWF Decompiler (70 Euro) and you can see all the code:

Sothink_SWF_Decompiler

Sothink SWF Decompiler

OF course there are tools that sell you that you can secure the swf, like:

  • Kindi secureSWF – the best one so far, but support answer kind of slow and after using some of the option it can break your swf or if you are using liquid design layout to fix it are preset width and height;
  • SWF Encrypt 7.0 – Amayeta – not support anymore, no money back guarantee – yes softwared did not worked and we wanted to get our money back, but in the end, nothing. Software was poorly made and now the website is down also.

So what best can be done is to create something and hope that it wont be broken, but to do it in a way, that taking the code out will be so painful process that the thief will think that there is no point.

One way to be do is by like, having 2 swf:

  1. Holder.swf
  2. RealApplication.swf

The idea is to load the RealApplication.swf into Holder.swf and apply different securities to both files:

  1. We shuffle the bytes in RealApplication.swf in a way, that it ca be re-arrange back if needed with a key. In this way when we open the RealApplication.swf in Sothink SWF Decompiler it will break it and not possible to be seen;
  2. We then create the Holder.swf to load it and re-arrange in a good way the bystes before showing – this will result in having the good version only in computer browser RAM and not as file;
  3. We make sure to obfuscate the swf of Holder.swf as much as possible so the real re-arranging mechanize to not be something too easy to understand and done with JAVA for example.

Tyring to open the RealApplication.swf in Sothink SWF Decompiler will show you:

 

Sothink SWF Decompiler with Snuffled bytes

Sothink SWF Decompiler with Snuffled bytes

Here is another great tutorial how this can be done in another way step by step by : Protect Your Flash Files From Decompilers by Using Encryption

Setting workflow for Node.js – development and staging

What we will have in the end:

 

Simple steps how to organize a local – development files. In this example, I take for granted that:

  1. You know what Node.js, SofeeScript, Sass is;
  2. Know and used Git.
  3. Have created a git respiratory in GitHub, BitBucket, else…

Now to the steps

Installations

1. Install Node.js;
2. Install Git
3. Install Ruby
4. Lets run the following commands in ruby:

1
2
3
gem update
gem install sass
gem install compass

Setup the local project dir and connect it to git respiratory

4. Create folder with name of the project. For case of example, lets name it “workflow”;
5. Open Node.js console and cd to the folder “worflow”. Example:

1
2
3
cd c:
c:\> cd workflow
cd:\workflow>;

6. Install the setup:

1
cd:\workflow>; npm init

7. You will be asked to fill the package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "workflow",
"version": "0.0.1",
"description": "Workflow setup",
"main": "app.js",
"dependencies": {},
"devDependencies": {},
"repository": {
"type": "git",
"url": "?.git"
},
"author": "Slav"
}

For version 0.0.1, and what to set there, you can learn more about at : Semantic Versioning 2.0.0

8. Now lets create the next folder structure:

  • builds
    • development – all our files, while we are programing the files
      • images – will hold all our images + hi-res images;
      • css – will hold our css that will be generated from Sass
      • js – will hold the single script.js file that will be generated from the CoffeeScript or Javascript originals;
    • production – hold the files when we are ready to upload them to the server
  • components
    • coffee – for out CoffeeScript files;
    • sass – all Sass files;
    • js – JavaScript Originals;

9. Optional: Add .gitignore file in the root folder. From time to time, there are files you don’t want Git to check in to Git respiratory. There are a few ways to tell Git which files to ignore. Read more on Ignoring files. I suggest adding:

1
2
3
.tmp
node_modules
.sass-cache

10. Optional add README.md for syntax check Markdown Cheatsheett

1
2
3
# Workflow

instructions to other developers

11. Initialize git respiratory. Open the GitBash or terminal, navigate to the project root folder and write:

1
2
3
4
5
git init
git add .
git commit -m "First setup"
git remote add origin http://github.com/some_url.git
git push -u origin master

In the last one the last you will be asked for the password. Now you are setup with the git.

Now we will need to create the workflows

We have 3 options:

  1. GRUNT.js – this one uses JSON file to manage tasks;
  2. gulpjs – use JS file to manage tasks; Uses pipes to manage the tasks.
  3. broccoli – use JS file to manage tasks; Uses threes – (dirs and sub dirs to manage tasks).

We will use gulp, as it my preferred choice. Also seems that Guilt have better community support than broccoli and a bit more plugins.

12. Now we need to install the gulp

1
2
cd:\workflow> npm install -g gulp
cd:\workflow> npm install --save-dev gulp

13. Now install gulp plugins

1
2
3
4
5
cd:\workflow> npm install --save-dev gulp-util
cd:\workflow> npm install --save-dev gulp-coffee
cd:\workflow> npm install --save-dev gulp-concat
cd:\workflow> npm install --save-dev gulp-browserify
cd:\workflow> npm install --save-dev gulp-compass
  • gulp-util – used to write similar to console.log() lines in terminal;
  • gulp-coffee – used to convert the CoffeeScript files to JavaScript files
  • gulp-concat – used to concat all JavaScript files into a single one;
  • gulp-browserify- add JavaScript libraries, using require(‘module’);
  • gulp-compass – ;
  • gulp-connect – ;

14. Now install JavaScript plugins

1
2
cd:\workflow> npm install --save-dev jquery
cd:\workflow> npm install --save-dev mustache
  • jquery- jQuery
  • mustache – ;

15. Crete gulpfile.js in the root project folder with the content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var gulp = require('gulp');
var gulputil = require('gulp-util');
var coffee = require('gulp-coffee');
var concat = require('gulp-concat');
var browserify = require('gulp-browserify');
var compass = require('gulp-compass');

// script files
var coffee_files = ['components/coffee/*.coffee'];
var javascript_files = ['components/js/*.js'];
var sass_main_file = ['components/sass/styles.scss'];
var sass_files = ['components/sass/*.scss'];

// 1. convert Coffee files to JavaScript files
// and move them to javascript folder
coffeeTask = function() {

    gulp
    .src(coffee_files)
    .pipe(
        coffee({bare: true}).on('error', gulputil.log)
    )
    .pipe(gulp.dest('component/js'))
}

// 2. contact all JavaScript files into scripts.js
// and move them to development scripts folder
// Note! We do keep files comments, offsets in order for easier debugging. For production we will minify it.
jsTask = function() {
    gulp.src(javascript_files)
    .pipe(concat('scripts.js'))
    .pipe(browserify())
    .pipe(gulp.dest('builds/development/js'))
}

// 3. create css files from the scss fules and put them in
// and move them to development css folder
compassTask = function() {
    gulp.src(sass_main_file)
    .pipe(
        compass({
            sass:   'components/sass',
            css:    'components/css',
            image:  'builds/development/images',
            style:  'expanded'
        })
        .on('error', gulputil.log))
    .pipe(gulp.dest('builds/development/css'))
}

gulp.task('coffee', coffeeTask);
gulp.task('js', jsTask);
gulp.task('compass', compassTask);

// any chagnes to the scripts will re-compile the files
watchTask = function () {
    gulp.watch(coffee_files, ['coffee']);
    gulp.watch(javascript_files, ['js']);
    gulp.watch(sass_files, ['compass']);
}

// gulp watch
gulp.task('watch', watchTask);

// default gulp
gulp.task('default', ['coffee', 'js', 'compass', 'watch']);

This will tell gulp what to do when it runs. For more information about gulp, see gulp API docs:

16. In the end the project file will be

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "workflow",
"version": "0.0.1",
"description": "Workflow setup",
"main": "app.js",
"dependencies": {},
"devDependencies": {
"gulp": "^3.9.0",
"gulp-coffee": "^2.3.1",
"gulp-concat": "^2.5.2",
"gulp-util": "^3.0.5"
},
"author": "Slav"
}

Actionctipt3 Base32Hex encode and decode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.streamingvideoprovider.player.utils {
   
    public class Base32 {
       
        /**
         * Number of bits stored in base32hex alphabet values
         *
         * @var int
         */

        protected static const base32HexBits:int = 5;
       
        /**
         * Base32hex lookup alphabet [wiki link](http://en.wikipedia.org/wiki/Base32#base32hex)
         *
         * @var array
         */

        protected static const base32HexAlphabet_letter:Array = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V"];
        protected static const base32HexAlphabet_bits:Array = ["00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000", "10001", "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010", "11011", "11100", "11101", "11110", "11111"];
       
       
        /**
         * Returns binary string representation for string
         *
         * Example: Base32.stringToBinary("Hello") will return "0100100001100101011011000110110001101111"
         *
         * @param string inputString
         *
         * @return string
         */

        public static function stringToBinary(inputString:String):String {
            var i:int = 0;
            const imax:int = inputString.length;
            var out:String = "";
            while (i < imax) {
                out += "0" + inputString.charCodeAt(i).toString(2);
                i++;
            }
            return out;
        }

        /**
         * Returns string representation from binary string
         *
         * Example: Base32.binaryToString("0100100001100101011011000110110001101111") will return "Hello"
         *
         * @param string inputString
         *
         * @return string
         */

        public static function binaryToString(inputString:String):String {
            var i:int = 0;
            var chunks:Array = str_split(inputString, 8);
            const imax:int = chunks.length;
            var out:String = "";
            while (i < imax) {
                out += String.fromCharCode(parseInt(chunks[i], 2));
                i++;
            }
            return out;
        }

        /**
         * Encode string into base32hex string
         *
         * Example: Base32.encode("Hello") will return "91IMOR3F"
         *
         * @param string inputString
         *
         * @return string
         */

        public static function encode(inputString:String) {
            var binStr:String = stringToBinary(inputString);
           
            // stuff string end with zero chars ('0') to be divisable by 5
            while (binStr.length % base32HexBits != 0) {
                binStr += "0";
            }
           
            const data:Array = str_split(binStr, base32HexBits);
            var i:int = 0, j:int = 0;
            const imax:int = data.length;
            const jmax:int = base32HexAlphabet_bits.length;
            var out:String = "";
            while (i < imax) {
                j = 0;
                while (j < jmax) {
                    if (base32HexAlphabet_bits[j] == data[i]) {
                        out += base32HexAlphabet_letter[j];
                        break;
                    }
                    j++;
                }
                i++;
            }
            return out;
        }

        /**
         * Decode base32hex string to string
         *
         * Example: Base32.decode("91IMOR3F") will return "Hello"
         *
         * @param string inputString
         *
         * @return string
         */

        public static function decode(inputString:String):String {
            const data:Array = inputString.toUpperCase().split("");
            var i:int = 0, j:int = 0;
            const imax:int = data.length;
            const jmax:int = base32HexAlphabet_letter.length;
            var out:String = "";
            while (i < imax) {
                j = 0;
                while (j < jmax) {
                    if (base32HexAlphabet_letter[j] == data[i]) {
                        out += base32HexAlphabet_bits[j];
                        break;
                    }
                    j++;
                }
                i++;
            }
           
            //remove last added bits
            out = out.substr(0, Math.floor(out.length / 8) * 8);
            trace(out);
           
            return binaryToString(out);
        }
       

        /**
         * Split strign in even parts
         *
         * @param string inputString
         * @param int splitIn
         *
         * @return array
         */

        private static function str_split(value:String, splitIn:int):Array {
            var out:Array = [];
            var i:int = 0;
            const imax:int = Math.ceil(value.length / splitIn);
            while (i < imax) {
                out[i] = value.substr(i * splitIn, splitIn);
                i++
            }
            return out;
        }
    }
}

Facebook default embed player size

By default when you embed youtube/veoh or other content. The default size the player get is: 487×274 or width=”487 height=”274

youtube's_embed_player_in_facebook

Cost Estimates by PMBOK®

A cost estimate is a prediction of what can be the total resources needed to complete task/project.
These are the defined estimates by PMBOK®:

  • Rough Order of Magnitude estimate = -50% to +50%
  • Preliminary estimate = -15% to + 50%
  • Budget estimate = -10% to +25%
  • Definitive estimate = -5% to +10%
  • Final estimate = 0%

My experience with CloudFlare services – Great disappointment

Recently I started to use CloudFlare for a project that demand better caching and faster user delivery of the content. I added CloudFlare to the website and paid pro plan. All seems to be set, quite easy configurations. About security I did not make it too aggressive, almost all moderate, jut to try it for a bit.

in 12 hours I start to get email request by UserVoice from users that are not able to access website. It seems that some of the users just can not make it to the website. Instead they get a blank page or Unable to comment message:

 

Unable to connectLooking it via TeamViwer from a PC of a user that had the problem,  seems that the server to point was nginx/0.7.67. Which was very strange as we then used Apache VPS. The empty page I see is pointing to a server with:

Unknown nginx server

Unknown nginx server

But when I open correctly the website, I should see it pointing to the cloudflare-nginx:

cloudflare server

cloudflare server

So very strange. I also notice that pausing my Cloudflare service, users are able now  to connect successfully. I of course submitted a ticket to the support, asking them for help and even provided a TeamViwer access to a laptop with this problem 24/7. And here comes my real problem. From submitting

My requests

My requests

this issue to the end it taken me 15 days! And in the end I was the one that found out the issue, and resolve the issue. CloudFlare support answered once in 24h,  sometimes they was really fast, like in 1-2 hours. In the end we exchange total of 45 messages. With them telling me that is is something on our servers that we are blocking the users. And that our system give users 500 http error.

So of course I create a test migration plan and we move the VPS website, database from one hosting provider to another, as CloudFlare support recommended. This cost us a lot of efforts for us, so users did not understand or have down time. You know this is important when you have website with more then 714 000 pages views per month. In the end this did not help us.

monthly page views

monthly page views

Second was the issue with the SSL certificates and that might cost us all the trouble. We were using GoDaddy certificate, we bought new, install it, but still issues. This at least was not recommended from them, but from a freelancer that had a lot of CloudFlare projects.

Third I send them coupe of time tracert commands from different locations from users that was not able to reach website. CloudFlare service assign 2 IPs to your domain, so I notice that from one IP users are able to connect, but all users that goes to the other IP did not. CloudFlare support suggested that the ISP of the users are blocking them. I contact the ISP and they let me know that they do not have any blocking against that IP or for CloudFlare network…

An by that time we started to try everything. I suggest CloudFlare support to change the IPs for a test, just to see if this was the issue. Reply was:

CloudFlare Answer

CloudFlare Answer

Of couce I’m not sure how they are dynamic if for the lat 15 days they did not change a bit, but anyway. First option was to cost me 200$ to test the business plan. Second option was a bit bad, because I have one account with all the websites, and the website was configured with page rules, and records. So by support just not do give me a test plan or something else I needed to move my account to another place and retest it again, also to plan how to do it without the users get down time as they give me different DNS from the new account

Solution

I created new account as this was the only option. Bought a pro plan. Transfer all my A, CNAME, MX, TXT records, all my page rules and settings. and linked the website from there. Then I change my domain DNS and voala in 24h all the users started to connect :)

In the end

I was not happy with their support. They did try to help me, but really half assed. I did not like their mindset that the issue is in us. I in future project I will look for alternatives for my customers. As this half assed support cost us time and expenses.

How to export large mysql database (from godaddy)?

I have about 50 MB gzipped database. Go daddy phpMyAmin allow me only 10 MB, so what to do? Use : http://www.mysqldumper.net/Really easy to do it after a setup. Just suggestion, pust some .htpasswd to protect it :)