Local Shared Object(Flashクッキー)

Local Shared Object(Flashクッキー)Local Shared Objectとはブラウザのクッキーのように、Flash専用のデータ保存の仕組みのこと。一般的にはアクセス履歴、カスタマイズ記録、ゲームの得点などの数キロバイト以内程度のデータ保存に使う。

例えば、音楽が鳴るサイトでは、ユーザーが音量をコントロールできる場合が多い。コンテンツ開設側はなるべく大きな音で強く印象付けたいところではあるが、ユーザーがそれを受け入れるとは限らない。現状、音量の調節機能をつけるのはコンテンツ開設側のマナーとなっている。
そして、音量調節機能をつけた場合でも、そのコンテンツへの二回目のアクセスや、同じサイト内の別なコンテンツへのアクセスそのたび毎の音量調節は煩雑な印象を与えることがある。

このような場合、Local Shared Objectを使って音量を記録しておき、そのPCからのアクセスでは自動的にユーザーの以前設定した値を取れば解決する。

以下は実行例(アクセスした回数と最終時刻を保存している。ブラウザからリロードすると回数が増える)

◆Local Shared Objectの特性
データはサイトのドメイン、ディレクトリ単位で設定(保存、参照、削除)が可能で、同じPCなら異なるブラウザであっても情報は共有される。
同じドメインであればswfやhtmlが異なっていても、設定が可能だが、他のドメインで保存したデータは参照できない。
設定ダイアログを表示できるようにステージサイズは215*138以上であること。

◆ローカルでの改変可能性
情報はローカルPC上のFlash専用領域に保存され、一般的なユーザーの触れない領域に保存するので改変の可能性はとても低い。ただし、経験のあるユーザーがツールを使えば改変も可能らしい(未確認)。
Windows Vistaでは以下のディレクトリのさらに中。
C:\ユーザー\[ユーザー名]\AppData\Roaming\Macromedia\Flash Player\#SharedObjects
「隠しフォルダを表示しない」のチェックをはずさないと見れない。

◆セキュリティリスク
ただし、こういった改変があったとしても、あくまでもFlashに入力したデータをPC上に保存し、Flashで再取得するための機能なので、個人情報やメールアドレスをメーラーやほかのアプリケーションから取得するなどのことはできない。
あるとしたら、ユーザーがFlashゲームの得点やパラメーターの保存値の改変をして、連動する処理に呉認識させることだろうが、例えばわざとランダムな文字列と一緒に保存してしまうと、外からは区別がつかないので改変も容易なことではない。
そもそもクリティカルな情報を照合なしでFlashのみで管理することは考えづらい。実質的には問題になることは無いだろう。

◆データ容量
一般的にはゲームの得点記録、ユーザーの選択、カスタマイズ項目の記録など、1キロバイト内のデータ保存に使う。
作られた趣旨としては恐らく大きくとも100KB程度の文字列、数字やデータ(配列、オブジェクト)の保存を目的としているので、大量のデータの保存には別な仕組みを使うことが一般的。

◆サーバー、DBとの比較、併用
サーバーへの保存とは異なる仕組みなので、Flashオーサリング作業だけで完結するので作りやすく、また運用に特別な費用がかからない。
ユーザー同士の得点順位、運用者への通知などは行えないので、データの送信機能が必要な場合はサーバー、データベースが必要。サーバーへの情報送信と併用も可能で、ID、パスワードはLocal Shared Object保存しておき、ログインの煩雑さを軽減させることも可能。

◆FlashMediaServer
実は「Local Shared Object」という名前のクラスは無い。「Shared Object」と言ったほうがクラス名としてはより正確だが、ローカルPCで使う部分に限定した解説なので、この語にした。
FlashMediaServerを使うとLocal Shared Objectとほぼ同じ要領でネット越しにユーザーのPC同士のデータ共有ができる。

【参考】
ローカル Shared Object とは
http://www.adobe.com/jp/support/kb/ts/228/ts_228630_ja-jp.html
http://www.adobe.com/jp/products/flashplayer/articles/lso/
flash.net.SharedObject (ActionScript 3.0)
http://livedocs.adobe.com/flash/9.0_jp/ActionScriptLangRefV3/flash/net/SharedObject.html
SharedObject クラス
http://www.1art.jp/flash/le/lesson41/lesson41.htm
Local Shared Object – wikipedia
http://ja.wikipedia.org/wiki/Local_Shared_Object

[sourcecode language=”as3″]
/*
*
* Local Shared Object
*/
package {
import flash.display.Sprite;
import flash.net.SharedObject;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.display.SimpleButton;
[SWF(backgroundColor="0xEEEEEE")]
public class Main extends Sprite {

private var so:SharedObject = SharedObject.getLocal("soDemo");
private var tf:TextField = Create.newTextField([8,8,stage.stageWidth-16,100],[["border",true]]);
private var sb:SimpleButton = Create.newSimpleButton([(stage.stageWidth-200)/2,120,200,24,"記録したアクセス履歴を削除する"]);
private var explanation:TextField = Create.newTextField([0,stage.stageHeight-280,stage.stageWidth,280],[["wordWrap",true]]);

//この関数がまず実行される。
public function Main() {
var date:Date = new Date();

if(so.data.count){
tf.text = "お使いのPCからの"+so.data.count+"回目のアクセスです。"+"\n前回のアクセスは"+so.data.date.fullYear+"年"+(so.data.date.month+1)+"月"+so.data.date.date+"日"+so.data.date.hours+"時"+so.data.date.minutes+"分"+so.data.date.seconds+"秒でした。";
}else{
tf.text = "ようこそ。お使いのPCからこのFlash(swf)へのアクセス記録はありません。";
so.data.count = 1;
}
tf.appendText("\n\n今回のアクセス時刻("+date.fullYear+"年"+(date.month+1)+"月"+date.date+"日"+date.hours+"時"+date.minutes+"分"+date.seconds+"秒)を記録します。");
tf.appendText("\nサーバーへの情報送信は行っていません。");

so.data.date = date;
so.data.count ++;
so.flush(100);

addChild(tf);
addChild(sb);

explanation.appendText("Local Shared Object");
explanation.appendText("\nLocal Shared Object(ローカル・シェアード・オブジェクト、LSO)とはAdobe社のFlash Player がユーザーPCの内部に保存するデータ形式である。 データは「Flashクッキー」とも呼ばれ、ブラウザのcookieのようにサイト毎の情報を保持する。 Macromedia Flash MX (Flash Player 6) 以降で実装されている。");
explanation.appendText("\n\n出典: フリー百科事典『ウィキペディア(Wikipedia)』\nhttp://ja.wikipedia.org/wiki/Local_Shared_Object");
addChild(explanation);
sb.addEventListener(MouseEvent.MOUSE_DOWN, MOUSE_DOWN);
}

private function MOUSE_DOWN(e:MouseEvent):void{
so.clear();
tf.text = "データを削除しました。";
}
}
}

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.text.TextFormat;
import flash.display.Sprite;
import flash.display.Shape;
import flash.display.SimpleButton;
class Create{
public static var defaultTextFormat:TextFormat = new TextFormat();

public static function newSimpleButton(x_y_w_h_txt:Array = null,property:Array=null,graphics:Array=null):SimpleButton{
var upState:Sprite = newSprite([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0xCCCCCC,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
upState.addChild(newShape([2,2],null,[["beginFill",[0xE5E5E5,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2]-4,x_y_w_h_txt[3]-4,6]]]))
var overState:Sprite = newSprite([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0xBBBBBB,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
overState.addChild(newShape([2,2],null,[["beginFill",[0xEEEEEE,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2]-4,x_y_w_h_txt[3]-4,6]]]))
var downState:Sprite = newSprite([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0xAAAAAA,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
downState.addChild(newShape([2,2],null,[["beginFill",[0xDDDDDD,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2]-4,x_y_w_h_txt[3]-4,6]]]))
var hitTestState:Shape = newShape([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
if(x_y_w_h_txt[4]){
upState.addChild(newTextField([0,2,x_y_w_h_txt[2],x_y_w_h_txt[3]-2],[["defaultTextFormat",new TextFormat("_sans", null, null, null, null, null, null, null, "center")],["text",x_y_w_h_txt[4]]]));
overState.addChild(newTextField([0,2,x_y_w_h_txt[2],x_y_w_h_txt[3]-2],[["defaultTextFormat",new TextFormat("_sans", null, null, null, null, null, null, null, "center")],["text",x_y_w_h_txt[4]]]));
downState.addChild(newTextField([0,3,x_y_w_h_txt[2],x_y_w_h_txt[3]-3],[["defaultTextFormat",new TextFormat("_sans", null, null, null, null, null, null, null, "center")],["text",x_y_w_h_txt[4]]]));
}
var sb:SimpleButton = new SimpleButton(upState,overState,downState,hitTestState);

return sb;
}

public static function newShape(x_y_w_h_sh:Array = null,property:Array=null,graphics:Array=null):Shape{
var i:int;
var sh:Shape;
if(x_y_w_h_sh && x_y_w_h_sh[4]){
sh = x_y_w_h_sh[4];
}else{
sh = new Shape();
}
if(x_y_w_h_sh){
if (x_y_w_h_sh[0]) { sh.x = x_y_w_h_sh[0] };
if (x_y_w_h_sh[1]) { sh.y = x_y_w_h_sh[1] };
}
if(property){
for (i = 0; i < property.length; i++) {
if(property[i] && property[i].length > 1){
sh[property[i][0]] = property[i][1];
}
}
}
if(graphics){
for (i = 0; i < graphics.length; i++) {
if(graphics[i] && graphics[i].length > 1){
sh.graphics[graphics[i][0]].apply(null, graphics[i][1]);
}
}

}
if(x_y_w_h_sh){
if (x_y_w_h_sh[2]) { sh.width = x_y_w_h_sh[2] };
if (x_y_w_h_sh[3]) { sh.height = x_y_w_h_sh[3] };
}
return sh;
}
public static function newSprite(x_y_w_h_sp:Array = null,property:Array=null,graphics:Array=null,addChild:DisplayObject = null):Sprite{
var i:int;
var sp:Sprite;
if(x_y_w_h_sp && x_y_w_h_sp[4]){
sp = x_y_w_h_sp[4];
}else{
sp = new Sprite();
}
if(x_y_w_h_sp){
if (x_y_w_h_sp[0]) { sp.x = x_y_w_h_sp[0] };
if (x_y_w_h_sp[1]) { sp.y = x_y_w_h_sp[1] };
}
if(property){
for (i = 0; i < property.length; i++) {
if(property[i] && property[i].length > 1){
sp[property[i][0]] = property[i][1];
}
}
}
if(graphics){
for (i = 0; i < graphics.length; i++) {
if(graphics[i] && graphics[i].length > 1){
sp.graphics[graphics[i][0]].apply(null, graphics[i][1]);
}
}

}
if(addChild){
sp.addChild(addChild);
}
if(x_y_w_h_sp){
if (x_y_w_h_sp[2]) { sp.width = x_y_w_h_sp[2] };
if (x_y_w_h_sp[3]) { sp.height = x_y_w_h_sp[3] };
}
return sp;
}

public static function newTextField(x_y_w_h_txt_color_alpha:Array = null,property:Array=null,method:Array=null):TextField{
var i:int;
var ta:TextField = new TextField();
ta.defaultTextFormat = defaultTextFormat;
if(x_y_w_h_txt_color_alpha){
if (x_y_w_h_txt_color_alpha[0]) { ta.x = x_y_w_h_txt_color_alpha[0] };
if (x_y_w_h_txt_color_alpha[1]) { ta.y = x_y_w_h_txt_color_alpha[1] };
if (x_y_w_h_txt_color_alpha[2]) { ta.width = x_y_w_h_txt_color_alpha[2] };
if (x_y_w_h_txt_color_alpha[3]) { ta.height = x_y_w_h_txt_color_alpha[3] };
if (x_y_w_h_txt_color_alpha[4]) { ta.text = x_y_w_h_txt_color_alpha[4] };
if (x_y_w_h_txt_color_alpha[5]) { ta.textColor = x_y_w_h_txt_color_alpha[5] };
if (x_y_w_h_txt_color_alpha[6]) { ta.alpha = x_y_w_h_txt_color_alpha[6] };
}
if(property){
for (i = 0; i < property.length; i++) {
if(property[i] && property[i].length > 1){
ta[property[i][0]] = property[i][1];
}
}
}
if(method){
for (i = 0; i < method.length; i++) {
if(method[i] && method[i].length > 1){
ta[i].apply(null, method[i][1]);
}
}
}
return ta;
}
}
[/sourcecode]